feat: friend tracker experiment
This commit is contained in:
@@ -23,8 +23,8 @@ import me.rhunk.snapenhance.bridge.BridgeService
|
||||
import me.rhunk.snapenhance.common.BuildConfig
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.MessageLoggerWrapper
|
||||
import me.rhunk.snapenhance.common.config.ModConfig
|
||||
import me.rhunk.snapenhance.e2ee.E2EEImplementation
|
||||
import me.rhunk.snapenhance.messaging.ModDatabase
|
||||
@@ -69,7 +69,8 @@ class RemoteSideContext(
|
||||
val scriptManager = RemoteScriptManager(this)
|
||||
val settingsOverlay = SettingsOverlay(this)
|
||||
val e2eeImplementation = E2EEImplementation(this)
|
||||
val messageLogger by lazy { MessageLoggerWrapper(androidContext.getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)) }
|
||||
val messageLogger by lazy { LoggerWrapper(androidContext.getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)) }
|
||||
val tracker = RemoteTracker(this)
|
||||
|
||||
//used to load bitmoji selfies and download previews
|
||||
val imageLoader by lazy {
|
||||
@@ -108,6 +109,7 @@ class RemoteSideContext(
|
||||
streaksReminder.init()
|
||||
scriptManager.init()
|
||||
messageLogger.init()
|
||||
tracker.init()
|
||||
config.root.messaging.messageLogger.takeIf {
|
||||
it.globalState == true
|
||||
}?.getAutoPurgeTime()?.let {
|
||||
|
||||
29
app/src/main/kotlin/me/rhunk/snapenhance/RemoteTracker.kt
Normal file
29
app/src/main/kotlin/me/rhunk/snapenhance/RemoteTracker.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import me.rhunk.snapenhance.bridge.logger.TrackerInterface
|
||||
import me.rhunk.snapenhance.common.data.TrackerEventsResult
|
||||
import me.rhunk.snapenhance.common.data.TrackerRule
|
||||
import me.rhunk.snapenhance.common.data.TrackerRuleEvent
|
||||
import me.rhunk.snapenhance.common.util.toSerialized
|
||||
|
||||
|
||||
class RemoteTracker(
|
||||
private val context: RemoteSideContext
|
||||
): TrackerInterface.Stub() {
|
||||
fun init() {
|
||||
/*TrackerEventType.entries.forEach { eventType ->
|
||||
val ruleId = context.modDatabase.addTrackerRule(TrackerFlags.TRACK or TrackerFlags.LOG or TrackerFlags.NOTIFY, null, null)
|
||||
context.modDatabase.addTrackerRuleEvent(ruleId, TrackerFlags.TRACK or TrackerFlags.LOG or TrackerFlags.NOTIFY, eventType.key)
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun getTrackedEvents(eventType: String): String? {
|
||||
val events = mutableMapOf<TrackerRule, MutableList<TrackerRuleEvent>>()
|
||||
|
||||
context.modDatabase.getTrackerEvents(eventType).forEach { (event, rule) ->
|
||||
events.getOrPut(rule) { mutableListOf() }.add(event)
|
||||
}
|
||||
|
||||
return TrackerEventsResult(events).toSerialized()
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,8 @@ class BridgeService : Service() {
|
||||
override fun getScriptingInterface() = remoteSideContext.scriptManager
|
||||
|
||||
override fun getE2eeInterface() = remoteSideContext.e2eeImplementation
|
||||
override fun getMessageLogger() = remoteSideContext.messageLogger
|
||||
override fun getLogger() = remoteSideContext.messageLogger
|
||||
override fun getTracker() = remoteSideContext.tracker
|
||||
override fun registerMessagingBridge(bridge: MessagingBridge) {
|
||||
messagingBridge = bridge
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.rhunk.snapenhance.messaging
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.common.data.FriendStreaks
|
||||
import me.rhunk.snapenhance.common.data.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.common.data.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.common.data.MessagingRuleType
|
||||
import me.rhunk.snapenhance.common.data.*
|
||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
|
||||
import me.rhunk.snapenhance.common.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.common.util.ktx.getLongOrNull
|
||||
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
class ModDatabase(
|
||||
@@ -67,6 +67,18 @@ class ModDatabase(
|
||||
"description VARCHAR",
|
||||
"author VARCHAR NOT NULL",
|
||||
"enabled BOOLEAN"
|
||||
),
|
||||
"tracker_rules" to listOf(
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
"flags INTEGER",
|
||||
"conversation_id CHAR(36)", // nullable
|
||||
"user_id CHAR(36)", // nullable
|
||||
),
|
||||
"tracker_rules_events" to listOf(
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
"flags INTEGER",
|
||||
"rule_id INTEGER",
|
||||
"event_type VARCHAR",
|
||||
)
|
||||
))
|
||||
}
|
||||
@@ -333,4 +345,90 @@ class ModDatabase(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTrackerRule(flags: Int, conversationId: String?, userId: String?): Int {
|
||||
return runBlocking {
|
||||
suspendCoroutine { continuation ->
|
||||
executeAsync {
|
||||
val id = database.insert("tracker_rules", null, ContentValues().apply {
|
||||
put("flags", flags)
|
||||
put("conversation_id", conversationId)
|
||||
put("user_id", userId)
|
||||
})
|
||||
continuation.resumeWith(Result.success(id.toInt()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTrackerRuleEvent(ruleId: Int, flags: Int, eventType: String) {
|
||||
executeAsync {
|
||||
database.execSQL("INSERT INTO tracker_rules_events (flags, rule_id, event_type) VALUES (?, ?, ?)", arrayOf(
|
||||
flags,
|
||||
ruleId,
|
||||
eventType
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun getTrackerRules(conversationId: String?, userId: String?): List<TrackerRule> {
|
||||
val rules = mutableListOf<TrackerRule>()
|
||||
|
||||
database.rawQuery("SELECT * FROM tracker_rules WHERE (conversation_id = ? OR conversation_id IS NULL) AND (user_id = ? OR user_id IS NULL)", arrayOf(conversationId, userId).filterNotNull().toTypedArray()).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
rules.add(
|
||||
TrackerRule(
|
||||
id = cursor.getInteger("id"),
|
||||
flags = cursor.getInteger("flags"),
|
||||
conversationId = cursor.getStringOrNull("conversation_id"),
|
||||
userId = cursor.getStringOrNull("user_id")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
fun getTrackerEvents(ruleId: Int): List<TrackerRuleEvent> {
|
||||
val events = mutableListOf<TrackerRuleEvent>()
|
||||
database.rawQuery("SELECT * FROM tracker_rules_events WHERE rule_id = ?", arrayOf(ruleId.toString())).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
events.add(
|
||||
TrackerRuleEvent(
|
||||
id = cursor.getInteger("id"),
|
||||
flags = cursor.getInteger("flags"),
|
||||
eventType = cursor.getStringOrNull("event_type") ?: continue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
fun getTrackerEvents(eventType: String): Map<TrackerRuleEvent, TrackerRule> {
|
||||
val events = mutableMapOf<TrackerRuleEvent, TrackerRule>()
|
||||
database.rawQuery("SELECT tracker_rules_events.id as event_id, tracker_rules_events.flags, tracker_rules_events.event_type, tracker_rules.conversation_id, tracker_rules.user_id " +
|
||||
"FROM tracker_rules_events " +
|
||||
"INNER JOIN tracker_rules " +
|
||||
"ON tracker_rules_events.rule_id = tracker_rules.id " +
|
||||
"WHERE event_type = ?", arrayOf(eventType)
|
||||
).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val trackerRule = TrackerRule(
|
||||
id = -1,
|
||||
flags = cursor.getInteger("flags"),
|
||||
conversationId = cursor.getStringOrNull("conversation_id"),
|
||||
userId = cursor.getStringOrNull("user_id")
|
||||
)
|
||||
val trackerRuleEvent = TrackerRuleEvent(
|
||||
id = cursor.getInteger("event_id"),
|
||||
flags = cursor.getInteger("flags"),
|
||||
eventType = cursor.getStringOrNull("event_type") ?: continue
|
||||
)
|
||||
events[trackerRuleEvent] = trackerRule
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.ui.manager.pages.FriendTrackerManagerRoot
|
||||
import me.rhunk.snapenhance.ui.manager.pages.LoggerHistoryRoot
|
||||
import me.rhunk.snapenhance.ui.manager.pages.TasksRoot
|
||||
import me.rhunk.snapenhance.ui.manager.pages.features.FeaturesRoot
|
||||
@@ -53,6 +54,7 @@ class Routes(
|
||||
val settings = route(RouteInfo("home_settings"), HomeSettings()).parent(home)
|
||||
val homeLogs = route(RouteInfo("home_logs"), HomeLogs()).parent(home)
|
||||
val loggerHistory = route(RouteInfo("logger_history"), LoggerHistoryRoot()).parent(home)
|
||||
val friendTracker = route(RouteInfo("friend_tracker"), FriendTrackerManagerRoot()).parent(home)
|
||||
|
||||
val social = route(RouteInfo("social", icon = Icons.Default.Group, primary = true), SocialRoot())
|
||||
val manageScope = route(RouteInfo("manage_scope/?scope={scope}&id={id}"), ManageScope()).parent(social)
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
package me.rhunk.snapenhance.ui.manager.pages
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.TrackerLog
|
||||
import me.rhunk.snapenhance.common.data.TrackerEventType
|
||||
import me.rhunk.snapenhance.common.data.TrackerRule
|
||||
import me.rhunk.snapenhance.common.data.TrackerRuleEvent
|
||||
import me.rhunk.snapenhance.ui.manager.Routes
|
||||
import me.rhunk.snapenhance.ui.util.pagerTabIndicatorOffset
|
||||
import java.text.DateFormat
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
class FriendTrackerManagerRoot : Routes.Route() {
|
||||
enum class FilterType {
|
||||
CONVERSATION, USERNAME, EVENT
|
||||
}
|
||||
|
||||
private val titles = listOf("Logs", "Config Rules")
|
||||
private var currentPage by mutableIntStateOf(0)
|
||||
|
||||
override val floatingActionButton: @Composable () -> Unit = {
|
||||
if (currentPage == 1) {
|
||||
ExtendedFloatingActionButton(
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = "Add Rule") },
|
||||
expanded = false,
|
||||
text = {},
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LogsTab() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val logs = remember { mutableStateListOf<TrackerLog>() }
|
||||
var lastTimestamp by remember { mutableLongStateOf(Long.MAX_VALUE) }
|
||||
var filterType by remember { mutableStateOf(FilterType.USERNAME) }
|
||||
|
||||
var filter by remember { mutableStateOf("") }
|
||||
var searchTimeoutJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
suspend fun loadNewLogs() {
|
||||
withContext(Dispatchers.IO) {
|
||||
logs.addAll(context.messageLogger.getLogs(lastTimestamp, filter = {
|
||||
when (filterType) {
|
||||
FilterType.USERNAME -> it.username.contains(filter, ignoreCase = true)
|
||||
FilterType.CONVERSATION -> it.conversationTitle?.contains(filter, ignoreCase = true) == true || (it.username == filter && !it.isGroup)
|
||||
FilterType.EVENT -> it.eventType.contains(filter, ignoreCase = true)
|
||||
}
|
||||
}).apply {
|
||||
lastTimestamp = minOfOrNull { it.timestamp } ?: lastTimestamp
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetAndLoadLogs() {
|
||||
logs.clear()
|
||||
lastTimestamp = Long.MAX_VALUE
|
||||
loadNewLogs()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
var showAutoComplete by remember { mutableStateOf(false) }
|
||||
ExposedDropdownMenuBox(expanded = showAutoComplete, onExpandedChange = { showAutoComplete = it }) {
|
||||
TextField(
|
||||
value = filter,
|
||||
onValueChange = {
|
||||
filter = it
|
||||
coroutineScope.launch {
|
||||
searchTimeoutJob?.cancel()
|
||||
searchTimeoutJob = coroutineScope.launch {
|
||||
delay(200)
|
||||
showAutoComplete = true
|
||||
resetAndLoadLogs()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = { Text("Search") },
|
||||
maxLines = 1,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1F)
|
||||
.menuAnchor()
|
||||
.padding(8.dp)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showAutoComplete, onDismissRequest = {
|
||||
showAutoComplete = false
|
||||
}, properties = PopupProperties(focusable = false)) {
|
||||
val suggestedEntries = remember(filter) {
|
||||
mutableStateListOf<String>()
|
||||
}
|
||||
|
||||
LaunchedEffect(filter) {
|
||||
suggestedEntries.addAll(when (filterType) {
|
||||
FilterType.USERNAME -> context.messageLogger.findUsername(filter)
|
||||
FilterType.CONVERSATION -> context.messageLogger.findConversation(filter) + context.messageLogger.findUsername(filter)
|
||||
FilterType.EVENT -> TrackerEventType.entries.filter { it.name.contains(filter, ignoreCase = true) }.map { it.key }
|
||||
}.take(5))
|
||||
}
|
||||
|
||||
suggestedEntries.forEach { entry ->
|
||||
DropdownMenuItem(onClick = {
|
||||
filter = entry
|
||||
coroutineScope.launch {
|
||||
resetAndLoadLogs()
|
||||
}
|
||||
showAutoComplete = false
|
||||
}, text = {
|
||||
Text(entry)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dropDownExpanded by remember { mutableStateOf(false) }
|
||||
ExposedDropdownMenuBox(expanded = dropDownExpanded, onExpandedChange = { dropDownExpanded = it }) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.menuAnchor()
|
||||
) {
|
||||
Text("Filter " + filterType.name, modifier = Modifier.padding(8.dp))
|
||||
}
|
||||
DropdownMenu(expanded = dropDownExpanded, onDismissRequest = {
|
||||
dropDownExpanded = false
|
||||
}) {
|
||||
FilterType.entries.forEach { type ->
|
||||
DropdownMenuItem(onClick = {
|
||||
filterType = type
|
||||
dropDownExpanded = false
|
||||
coroutineScope.launch {
|
||||
resetAndLoadLogs()
|
||||
}
|
||||
}, text = {
|
||||
Text(type.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
item {
|
||||
if (logs.isEmpty()) {
|
||||
Text("No logs found", modifier = Modifier.padding(16.dp).fillMaxWidth(), textAlign = TextAlign.Center, fontWeight = FontWeight.Light)
|
||||
}
|
||||
}
|
||||
items(logs) { log ->
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(log.username + " " + log.eventType + " in " + log.conversationTitle)
|
||||
Text(
|
||||
DateFormat.getDateTimeInstance().format(log.timestamp),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Light
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedIconButton(
|
||||
onClick = {
|
||||
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Default.DeleteOutline, contentDescription = "Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LaunchedEffect(lastTimestamp) {
|
||||
loadNewLogs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
private fun ConfigRulesTab() {
|
||||
val rules = remember { mutableStateListOf<TrackerRule>() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
items(rules) { rule ->
|
||||
val events = remember(rule.id) {
|
||||
mutableStateListOf<TrackerRuleEvent>()
|
||||
}
|
||||
|
||||
LaunchedEffect(rule.id) {
|
||||
withContext(Dispatchers.IO) {
|
||||
events.addAll(context.modDatabase.getTrackerEvents(rule.id))
|
||||
}
|
||||
}
|
||||
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Text("Rule: ${rule.id} - conversationId: ${rule.conversationId?.let { "present" } ?: "none" } - userId: ${rule.userId?.let { "present" } ?: "none"}")
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||
) {
|
||||
events.forEach { event ->
|
||||
Text("${event.eventType} - ${event.flags}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
rules.addAll(context.modDatabase.getTrackerRules(null, null))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
override val content: @Composable (NavBackStackEntry) -> Unit = {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState { titles.size }
|
||||
currentPage = pagerState.currentPage
|
||||
|
||||
Column {
|
||||
TabRow(selectedTabIndex = pagerState.currentPage, indicator = { tabPositions ->
|
||||
TabRowDefaults.Indicator(
|
||||
Modifier.pagerTabIndicatorOffset(
|
||||
pagerState = pagerState,
|
||||
tabPositions = tabPositions
|
||||
)
|
||||
)
|
||||
}) {
|
||||
titles.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = pagerState.currentPage == index,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
modifier = Modifier.weight(1f),
|
||||
state = pagerState
|
||||
) { page ->
|
||||
when (page) {
|
||||
0 -> LogsTab()
|
||||
1 -> ConfigRulesTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LoggedMessage
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.MessageLoggerWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper
|
||||
import me.rhunk.snapenhance.common.data.ContentType
|
||||
import me.rhunk.snapenhance.common.data.download.*
|
||||
import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
|
||||
@@ -40,7 +40,7 @@ import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
class LoggerHistoryRoot : Routes.Route() {
|
||||
private lateinit var messageLoggerWrapper: MessageLoggerWrapper
|
||||
private lateinit var loggerWrapper: LoggerWrapper
|
||||
private var selectedConversation by mutableStateOf<String?>(null)
|
||||
private var stringFilter by mutableStateOf("")
|
||||
private var reverseOrder by mutableStateOf(true)
|
||||
@@ -182,7 +182,7 @@ class LoggerHistoryRoot : Routes.Route() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override val content: @Composable (NavBackStackEntry) -> Unit = {
|
||||
LaunchedEffect(Unit) {
|
||||
messageLoggerWrapper = MessageLoggerWrapper(
|
||||
loggerWrapper = LoggerWrapper(
|
||||
context.androidContext.getDatabasePath("message_logger.db")
|
||||
)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ class LoggerHistoryRoot : Routes.Route() {
|
||||
LaunchedEffect(Unit) {
|
||||
conversations.clear()
|
||||
withContext(Dispatchers.IO) {
|
||||
conversations.addAll(messageLoggerWrapper.getAllConversations())
|
||||
conversations.addAll(loggerWrapper.getAllConversations())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ class LoggerHistoryRoot : Routes.Route() {
|
||||
}
|
||||
LaunchedEffect(Unit, selectedConversation, stringFilter, reverseOrder) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val newMessages = messageLoggerWrapper.fetchMessages(
|
||||
val newMessages = loggerWrapper.fetchMessages(
|
||||
selectedConversation ?: return@withContext,
|
||||
lastFetchMessageTimestamp,
|
||||
30,
|
||||
|
||||
@@ -271,6 +271,18 @@ class HomeRoot : Routes.Route() {
|
||||
updateInstallationSummary(coroutineScope)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
ElevatedButton(onClick = {
|
||||
routes.friendTracker.navigate()
|
||||
}) {
|
||||
Text(text = "Friend Tracker", fontSize = 18.sp)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
installationSummary?.let { SummaryCards(installationSummary = it) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user