Auto Open stabilization
This commit is contained in:
@@ -573,8 +573,12 @@ class FeaturesRootSection : Routes.Route() {
|
|||||||
}
|
}
|
||||||
DataProcessors.Type.STRING, DataProcessors.Type.INTEGER, DataProcessors.Type.FLOAT -> {
|
DataProcessors.Type.STRING, DataProcessors.Type.INTEGER, DataProcessors.Type.FLOAT -> {
|
||||||
val isMessageListProperty = property.key.name.endsWith("_messages")
|
val isMessageListProperty = property.key.name.endsWith("_messages")
|
||||||
|
val isSleepWindowProperty = property.key.name.contains("sleep_window")
|
||||||
|
|
||||||
if (isMessageListProperty) {
|
if (isMessageListProperty) {
|
||||||
alertDialogs.MessageListPropertyDialog(property) { showDialog = false }
|
alertDialogs.MessageListPropertyDialog(property) { showDialog = false }
|
||||||
|
} else if (isSleepWindowProperty) {
|
||||||
|
alertDialogs.AutoOpenScheduleDialog(property as PropertyPair<String>) { showDialog = false }
|
||||||
} else {
|
} else {
|
||||||
alertDialogs.KeyboardInputDialog(property) { showDialog = false }
|
alertDialogs.KeyboardInputDialog(property) { showDialog = false }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -27,6 +28,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -80,6 +82,146 @@ import me.eternal.purrfectsnap.ui.util.Dialog as StandardDialog
|
|||||||
class AlertDialogs(
|
class AlertDialogs(
|
||||||
private val translation: LocaleWrapper,
|
private val translation: LocaleWrapper,
|
||||||
){
|
){
|
||||||
|
@Composable
|
||||||
|
fun MessageListPropertyDialog(property: PropertyPair<*>, onDismiss: () -> Unit = {}) {
|
||||||
|
val currentValue = property.value.getNullable()?.toString() ?: "[]"
|
||||||
|
val propertyName = translation[property.key.propertyName()]
|
||||||
|
|
||||||
|
MessageListManagerDialog(
|
||||||
|
title = propertyName ?: "",
|
||||||
|
messageListJson = currentValue,
|
||||||
|
onSave = { newValue: String ->
|
||||||
|
property.value.setAny(newValue)
|
||||||
|
},
|
||||||
|
onDismiss = onDismiss
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AutoOpenScheduleDialog(
|
||||||
|
property: PropertyPair<String>,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
val windowParts = (property.value.get() as String).split("-")
|
||||||
|
val startTime = windowParts.getOrNull(0)?.split(":") ?: listOf("23", "00")
|
||||||
|
val endTime = windowParts.getOrNull(1)?.split(":") ?: listOf("07", "00")
|
||||||
|
|
||||||
|
var isEditingEnd by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val startState = rememberTimePickerState(
|
||||||
|
initialHour = startTime.getOrNull(0)?.toIntOrNull() ?: 23,
|
||||||
|
initialMinute = startTime.getOrNull(1)?.toIntOrNull() ?: 0,
|
||||||
|
is24Hour = true
|
||||||
|
)
|
||||||
|
val endState = rememberTimePickerState(
|
||||||
|
initialHour = endTime.getOrNull(0)?.toIntOrNull() ?: 7,
|
||||||
|
initialMinute = endTime.getOrNull(1)?.toIntOrNull() ?: 0,
|
||||||
|
is24Hour = true
|
||||||
|
)
|
||||||
|
|
||||||
|
DefaultDialogCard {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(18.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = translation["auto_open_snaps.auto_open_schedule.title"] ?: "Auto Open Scheduler",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.ExtraBold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(Color.White.copy(alpha = 0.05f))
|
||||||
|
.padding(4.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
val activeColor = PurrfectPalette.glowPrimary.copy(alpha = 0.25f)
|
||||||
|
val inactiveColor = Color.Transparent
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(if (!isEditingEnd) activeColor else inactiveColor)
|
||||||
|
.clickable { isEditingEnd = false }
|
||||||
|
.padding(vertical = 10.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${translation["auto_open_snaps.auto_open_schedule.start"] ?: "Start"}: ${String.format("%02d:%02d", startState.hour, startState.minute)}",
|
||||||
|
color = if (!isEditingEnd) Color.White else Color.White.copy(alpha = 0.6f),
|
||||||
|
fontWeight = if (!isEditingEnd) FontWeight.Bold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(if (isEditingEnd) activeColor else inactiveColor)
|
||||||
|
.clickable { isEditingEnd = true }
|
||||||
|
.padding(vertical = 10.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${translation["auto_open_snaps.auto_open_schedule.end"] ?: "End"}: ${String.format("%02d:%02d", endState.hour, endState.minute)}",
|
||||||
|
color = if (isEditingEnd) Color.White else Color.White.copy(alpha = 0.6f),
|
||||||
|
fontWeight = if (isEditingEnd) FontWeight.Bold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimePicker(
|
||||||
|
state = if (isEditingEnd) endState else startState,
|
||||||
|
colors = TimePickerDefaults.colors(
|
||||||
|
clockDialColor = Color.White.copy(alpha = 0.05f),
|
||||||
|
clockDialSelectedContentColor = Color.White,
|
||||||
|
clockDialUnselectedContentColor = Color.White.copy(alpha = 0.7f),
|
||||||
|
selectorColor = PurrfectPalette.glowPrimary,
|
||||||
|
periodSelectorBorderColor = PurrfectPalette.glowPrimary,
|
||||||
|
periodSelectorSelectedContainerColor = PurrfectPalette.glowPrimary.copy(alpha = 0.2f),
|
||||||
|
periodSelectorUnselectedContainerColor = Color.Transparent,
|
||||||
|
periodSelectorSelectedContentColor = Color.White,
|
||||||
|
periodSelectorUnselectedContentColor = Color.White.copy(alpha = 0.7f),
|
||||||
|
timeSelectorSelectedContainerColor = PurrfectPalette.glowPrimary.copy(alpha = 0.2f),
|
||||||
|
timeSelectorUnselectedContainerColor = Color.White.copy(alpha = 0.05f),
|
||||||
|
timeSelectorSelectedContentColor = Color.White,
|
||||||
|
timeSelectorUnselectedContentColor = Color.White.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End)
|
||||||
|
) {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(text = translation["button.negative"], color = Color.White)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val startStr = String.format("%02d:%02d", startState.hour, startState.minute)
|
||||||
|
val endStr = String.format("%02d:%02d", endState.hour, endState.minute)
|
||||||
|
property.value.setAny("$startStr-$endStr")
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = PurrfectPalette.glowPrimary.copy(alpha = 0.35f),
|
||||||
|
contentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = translation["button.positive"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultDialogCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
fun DefaultDialogCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
@@ -1260,23 +1402,7 @@ class AlertDialogs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MessageListPropertyDialog(property: PropertyPair<*>, onDismiss: () -> Unit = {}) {
|
|
||||||
val currentValue = property.value.getNullable()?.toString() ?: "[]"
|
|
||||||
val propertyName = translation[property.key.propertyName()]
|
|
||||||
|
|
||||||
MessageListManagerDialog(
|
|
||||||
title = propertyName,
|
|
||||||
messageListJson = currentValue,
|
|
||||||
onSave = { newValue ->
|
|
||||||
property.value.setAny(newValue)
|
|
||||||
},
|
|
||||||
onDismiss = onDismiss
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageListManagerDialog(
|
fun MessageListManagerDialog(
|
||||||
@@ -1313,7 +1439,6 @@ class AlertDialogs(
|
|||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message list
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -1329,7 +1454,7 @@ class AlertDialogs(
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = translation["auto_reply_messages.dialog.no_messages"],
|
text = translation["bulk_messaging_action.no_messages_found"] ?: "No messages",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@@ -1371,24 +1496,14 @@ class AlertDialogs(
|
|||||||
showAddDialog = true
|
showAddDialog = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(Icons.Default.Edit, contentDescription = translation["common.edit"] ?: "Edit", tint = MaterialTheme.colorScheme.primary)
|
||||||
Icons.Default.Edit,
|
|
||||||
contentDescription = "Edit",
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
messageList = messageList.toMutableList().apply {
|
messageList = messageList.toMutableList().apply { removeAt(index) }
|
||||||
removeAt(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(Icons.Default.Delete, contentDescription = translation["common.delete"] ?: "Delete", tint = MaterialTheme.colorScheme.error)
|
||||||
Icons.Default.Delete,
|
|
||||||
contentDescription = "Delete",
|
|
||||||
tint = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1398,7 +1513,6 @@ class AlertDialogs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add button
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
editingIndex = -1
|
editingIndex = -1
|
||||||
@@ -1408,20 +1522,13 @@ class AlertDialogs(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
Icons.Default.Add,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(text = translation["auto_reply_messages.dialog.add_message"])
|
Text(text = translation["common.add"] ?: "Add Message")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialog buttons
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -1430,17 +1537,14 @@ class AlertDialogs(
|
|||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { onDismiss() },
|
onClick = { onDismiss() },
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(text = translation["button.cancel"])
|
Text(text = translation["button.cancel"])
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val gson = com.google.gson.Gson()
|
val gson = com.google.gson.Gson()
|
||||||
val jsonString = gson.toJson(messageList)
|
onSave(gson.toJson(messageList))
|
||||||
onSave(jsonString)
|
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -1450,7 +1554,6 @@ class AlertDialogs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add/Edit message dialog
|
|
||||||
if (showAddDialog) {
|
if (showAddDialog) {
|
||||||
StandardDialog(
|
StandardDialog(
|
||||||
onDismissRequest = { showAddDialog = false },
|
onDismissRequest = { showAddDialog = false },
|
||||||
@@ -1460,7 +1563,7 @@ class AlertDialogs(
|
|||||||
) {
|
) {
|
||||||
DefaultDialogCard {
|
DefaultDialogCard {
|
||||||
Text(
|
Text(
|
||||||
text = if (editingIndex == -1) translation["auto_reply_messages.dialog.add_message"] else translation["auto_reply_messages.dialog.edit_message"],
|
text = if (editingIndex == -1) translation["common.add"] ?: "Add Message" else translation["common.edit"] ?: "Edit Message",
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -1472,13 +1575,13 @@ class AlertDialogs(
|
|||||||
TextField(
|
TextField(
|
||||||
value = editingText,
|
value = editingText,
|
||||||
onValueChange = { editingText = it },
|
onValueChange = { editingText = it },
|
||||||
label = { Text(translation["auto_reply_messages.dialog.message_label"]) },
|
label = { Text(translation["common.message"] ?: "Message") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
minLines = 2,
|
minLines = 2,
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
placeholder = { Text(translation["auto_reply_messages.dialog.message_placeholder"]) }
|
placeholder = { Text(translation["common.type_message"] ?: "Type message...") }
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -1489,32 +1592,22 @@ class AlertDialogs(
|
|||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { showAddDialog = false },
|
onClick = { showAddDialog = false },
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(text = translation["button.cancel"])
|
Text(text = translation["button.cancel"])
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (editingText.isNotBlank()) {
|
if (editingText.isNotBlank()) {
|
||||||
if (editingIndex == -1) {
|
messageList = messageList.toMutableList().apply {
|
||||||
// Add new message
|
if (editingIndex == -1) add(editingText) else set(editingIndex, editingText)
|
||||||
messageList = messageList.toMutableList().apply {
|
|
||||||
add(editingText)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Edit existing message
|
|
||||||
messageList = messageList.toMutableList().apply {
|
|
||||||
set(editingIndex, editingText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showAddDialog = false
|
showAddDialog = false
|
||||||
},
|
},
|
||||||
enabled = editingText.isNotBlank()
|
enabled = editingText.isNotBlank()
|
||||||
) {
|
) {
|
||||||
Text(text = if (editingIndex == -1) translation["auto_reply_messages.dialog.add_message"] else translation["button.save"])
|
Text(text = if (editingIndex == -1) translation["common.add"] ?: "Add" else translation["button.save"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1522,3 +1615,4 @@ class AlertDialogs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"setup": {
|
"setup": {
|
||||||
"activity": {
|
"activity": {
|
||||||
"wrong_apk_title": "Wrong APK installed",
|
"wrong_apk_title": "Wrong APK installed",
|
||||||
@@ -1661,10 +1661,26 @@
|
|||||||
"name": "Auto Open Compact Notification",
|
"name": "Auto Open Compact Notification",
|
||||||
"description": "Use a smaller, single-line notification for status updates"
|
"description": "Use a smaller, single-line notification for status updates"
|
||||||
},
|
},
|
||||||
|
"show_progress_bar": {
|
||||||
|
"name": "Show Progress Bar",
|
||||||
|
"description": "Display a visual progress bar in the status notification"
|
||||||
|
},
|
||||||
|
"show_lifetime_stats": {
|
||||||
|
"name": "Show Lifetime Statistics",
|
||||||
|
"description": "Include the total number of snaps opened since installation in the notification"
|
||||||
|
},
|
||||||
|
"show_queue_preview": {
|
||||||
|
"name": "Show Queue Preview",
|
||||||
|
"description": "Show a list of the most recent snaps waiting in the queue (Expanded only)"
|
||||||
|
},
|
||||||
"only_on_wifi": { "name": "Auto Open only on Wi-Fi", "description": "Only process queue when connected to a Wi-Fi network to save mobile data" },
|
"only_on_wifi": { "name": "Auto Open only on Wi-Fi", "description": "Only process queue when connected to a Wi-Fi network to save mobile data" },
|
||||||
"only_when_idle": {
|
"only_when_idle": {
|
||||||
"name": "Auto Open only when Idle",
|
"name": "Auto Open Schedule",
|
||||||
"description": "Only process queue when the device is not in active use"
|
"description": "Configure a specific time window where the engine will throttle its speed."
|
||||||
|
},
|
||||||
|
"sleep_window": {
|
||||||
|
"name": "Auto Open Scheduler",
|
||||||
|
"description": "Define the start and end times for scheduled throttled processing."
|
||||||
},
|
},
|
||||||
"pause_during_gaming": { "name": "Pause Auto Open During Gaming", "description": "Automatically slow down processing when a resource intensive app or a game is in the foreground" },
|
"pause_during_gaming": { "name": "Pause Auto Open During Gaming", "description": "Automatically slow down processing when a resource intensive app or a game is in the foreground" },
|
||||||
"safe_processing": { "name": "Auto Open with stealth pace", "description": "Adds variable delays and natural breaks to remain undetected. Turn off for maximum speed." }
|
"safe_processing": { "name": "Auto Open with stealth pace", "description": "Adds variable delays and natural breaks to remain undetected. Turn off for maximum speed." }
|
||||||
@@ -2165,7 +2181,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"network_optimization": { "name": "Network Optimization", "description": "Optimizes network socket buffers for higher throughput" }, "better_transcript": {
|
"network_optimization": { "name": "Improved Network Connectivity", "description": "Optimizes network socket buffers for maximum stability and high-speed upload/download performance" }, "better_transcript": {
|
||||||
"name": "Better Transcript",
|
"name": "Better Transcript",
|
||||||
"description": "Improves the voice note transcript",
|
"description": "Improves the voice note transcript",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3257,6 +3273,11 @@
|
|||||||
"forced_logout_toast": "Removed account due to forced logout"
|
"forced_logout_toast": "Removed account due to forced logout"
|
||||||
},
|
},
|
||||||
"auto_open_snaps": { "title": "Auto Open Snaps", "processed_count": "Opened", "queue_size": "Queue", "action_reset": "Reset Statistics", "priority_title": "Auto Open Snaps (Priority)",
|
"auto_open_snaps": { "title": "Auto Open Snaps", "processed_count": "Opened", "queue_size": "Queue", "action_reset": "Reset Statistics", "priority_title": "Auto Open Snaps (Priority)",
|
||||||
|
"auto_open_schedule": {
|
||||||
|
"title": "Auto Open Scheduler",
|
||||||
|
"start": "Start",
|
||||||
|
"end": "End"
|
||||||
|
},
|
||||||
"error_title": "Auto Open Snaps (Errors)",
|
"error_title": "Auto Open Snaps (Errors)",
|
||||||
"channel_description": "Notifications for auto-opening snaps queue status",
|
"channel_description": "Notifications for auto-opening snaps queue status",
|
||||||
"priority_channel_description": "High priority notifications for auto-opening snaps",
|
"priority_channel_description": "High priority notifications for auto-opening snaps",
|
||||||
@@ -3277,6 +3298,16 @@
|
|||||||
"status_paused": "Paused",
|
"status_paused": "Paused",
|
||||||
"status_monitoring": "Monitoring",
|
"status_monitoring": "Monitoring",
|
||||||
"status_active": "Active",
|
"status_active": "Active",
|
||||||
|
"status_failed": "Failed to open {sender}",
|
||||||
|
"status_retrying": "Retrying in background...",
|
||||||
|
"processing_speed_full": "Full Speed",
|
||||||
|
"processing_speed": "Processing Speed",
|
||||||
|
"speed_throttled": "Throttled",
|
||||||
|
"estimated_time": "Estimated Time",
|
||||||
|
"notification_statistics": "STATISTICS",
|
||||||
|
"notification_total_opened": "Lifetime Opened",
|
||||||
|
"notification_queue_preview": "QUEUE PREVIEW",
|
||||||
|
"notification_no_snaps_queue": "Monitoring snaps in background...",
|
||||||
"queue_cleared": "Queue cleared and statistics reset",
|
"queue_cleared": "Queue cleared and statistics reset",
|
||||||
"queue_cleared_title": "Queue cleared",
|
"queue_cleared_title": "Queue cleared",
|
||||||
"queue_cleared_reset": "Queue Cleared & Reset",
|
"queue_cleared_reset": "Queue Cleared & Reset",
|
||||||
@@ -3686,17 +3717,25 @@
|
|||||||
"include_saved_locations_description": "Export your saved location coordinates",
|
"include_saved_locations_description": "Export your saved location coordinates",
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"quit": "Quit",
|
"quit": "Quit",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
"message": "Message",
|
||||||
|
"type_message": "Type message...",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"unknown_error": "Unknown error",
|
"unknown_error": "Unknown error",
|
||||||
"not_available": "N/A",
|
"not_available": "N/A",
|
||||||
"added": "Added",
|
"added": "Added",
|
||||||
"no_friends_found": "No friends found",
|
"no_friends_found": "No friends found",
|
||||||
|
"no_messages": "No messages",
|
||||||
|
"message": "Message",
|
||||||
|
"type_message": "Type message...",
|
||||||
"exporting_memories": "Exporting memories... ({failed} failed)"
|
"exporting_memories": "Exporting memories... ({failed} failed)"
|
||||||
},
|
},
|
||||||
"clear_friend_feed": "Clear Friend Feed",
|
"clear_friend_feed": "Clear Friend Feed",
|
||||||
|
|||||||
@@ -176,12 +176,19 @@ class MessagingTweaks : ConfigContainer() {
|
|||||||
inputCheck = { it.toIntOrNull()?.coerceAtLeast(1000) != null }
|
inputCheck = { it.toIntOrNull()?.coerceAtLeast(1000) != null }
|
||||||
}
|
}
|
||||||
val compactNotification = boolean("compact_notification", false)
|
val compactNotification = boolean("compact_notification", false)
|
||||||
|
val showProgressBar = boolean("show_progress_bar", true)
|
||||||
|
val showLifetimeStats = boolean("show_lifetime_stats", false)
|
||||||
|
val showQueuePreview = boolean("show_queue_preview", true)
|
||||||
|
|
||||||
// Resource Intelligence: Smart triggers for battery and data safety
|
// Resource Intelligence: Smart triggers for battery and data safety
|
||||||
val onlyOnWifi = boolean("only_on_wifi", false)
|
val onlyOnWifi = boolean("only_on_wifi", false)
|
||||||
val onlyWhenIdle = boolean("only_when_idle", false)
|
|
||||||
val pauseDuringGaming = boolean("pause_during_gaming", false)
|
val pauseDuringGaming = boolean("pause_during_gaming", false)
|
||||||
val safeProcessing = boolean("safe_processing", true)
|
val safeProcessing = boolean("safe_processing", true)
|
||||||
|
val onlyWhenIdle = boolean("only_when_idle", false)
|
||||||
|
val sleepWindow = string("sleep_window", defaultValue = "23:00-07:00") {
|
||||||
|
addFlags(ConfigFlag.NO_DISABLE_KEY)
|
||||||
|
inputCheck = { it.matches(Regex("^([01]\\d|2[0-3]):([0-5]\\d)-([01]\\d|2[0-3]):([0-5]\\d)$")) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutoDeleteSentMessagesConfig : ConfigContainer(hasGlobalState = true) {
|
class AutoDeleteSentMessagesConfig : ConfigContainer(hasGlobalState = true) {
|
||||||
@@ -324,4 +331,3 @@ class MessagingTweaks : ConfigContainer() {
|
|||||||
|
|
||||||
val instantTranslation = container("instant_translation", InstantTranslationConfig()) { requireRestart() }
|
val instantTranslation = container("instant_translation", InstantTranslationConfig()) { requireRestart() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ enum class FileType(
|
|||||||
JPG("jpg", "image/jpg",false, true, false),
|
JPG("jpg", "image/jpg",false, true, false),
|
||||||
ZIP("zip", "application/zip", false, false, false),
|
ZIP("zip", "application/zip", false, false, false),
|
||||||
WEBP("webp", "image/webp", false, true, false),
|
WEBP("webp", "image/webp", false, true, false),
|
||||||
|
HEIC("heic", "image/heic", false, true, false),
|
||||||
|
HEIF("heif", "image/heif", false, true, false),
|
||||||
MPD("mpd", "text/xml", false, false, false),
|
MPD("mpd", "text/xml", false, false, false),
|
||||||
UNKNOWN("dat", "application/octet-stream", false, false, false);
|
UNKNOWN("dat", "application/octet-stream", false, false, false);
|
||||||
|
|
||||||
@@ -64,6 +66,11 @@ enum class FileType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val majorBrand = String(array, 8, 4, Charsets.US_ASCII).trim('\u0000').lowercase()
|
val majorBrand = String(array, 8, 4, Charsets.US_ASCII).trim('\u0000').lowercase()
|
||||||
|
|
||||||
|
// Explicitly exclude known IMAGE-only brands to prevent false positives (HEIC/HEIF)
|
||||||
|
val imageBrands = setOf("heic", "heix", "hevc", "hevx", "mif1", "msf1")
|
||||||
|
if (majorBrand in imageBrands) return false
|
||||||
|
|
||||||
return majorBrand in setOf(
|
return majorBrand in setOf(
|
||||||
"mp41",
|
"mp41",
|
||||||
"mp42",
|
"mp42",
|
||||||
@@ -76,14 +83,13 @@ enum class FileType(
|
|||||||
"avc1",
|
"avc1",
|
||||||
"dash",
|
"dash",
|
||||||
"cmfc",
|
"cmfc",
|
||||||
"mif1",
|
|
||||||
"msnv",
|
"msnv",
|
||||||
"3gp4",
|
"3gp4",
|
||||||
"3gp5",
|
"3gp5",
|
||||||
"3gp6",
|
"3gp6",
|
||||||
"3g2a",
|
"3g2a",
|
||||||
"3g2b"
|
"3g2b"
|
||||||
) || majorBrand.isNotEmpty() // FALLBACK: If it has the ftyp box, it's a video
|
) || majorBrand.isNotEmpty() // FALLBACK: If it has the ftyp box and isn't a known image brand, it's a video
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromFile(file: File): FileType {
|
fun fromFile(file: File): FileType {
|
||||||
@@ -98,8 +104,24 @@ enum class FileType(
|
|||||||
val headerBytes = ByteArray(16)
|
val headerBytes = ByteArray(16)
|
||||||
System.arraycopy(array, 0, headerBytes, 0, 16)
|
System.arraycopy(array, 0, headerBytes, 0, 16)
|
||||||
val hex = bytesToHex(headerBytes)
|
val hex = bytesToHex(headerBytes)
|
||||||
return fileSignatures.entries.firstOrNull { hex.startsWith(it.key) }?.value
|
|
||||||
?: if (looksLikeIsoBmffVideo(headerBytes)) MP4 else UNKNOWN
|
// 1. Check strict signatures
|
||||||
|
fileSignatures.entries.firstOrNull { hex.startsWith(it.key) }?.value?.let { return it }
|
||||||
|
|
||||||
|
// 2. Check ISO BMFF container type
|
||||||
|
val majorBrand = if (headerBytes.size >= 12 &&
|
||||||
|
headerBytes[4] == 'f'.code.toByte() && headerBytes[5] == 't'.code.toByte() &&
|
||||||
|
headerBytes[6] == 'y'.code.toByte() && headerBytes[7] == 'p'.code.toByte()) {
|
||||||
|
String(headerBytes, 8, 4, Charsets.US_ASCII).trim('\u0000').lowercase()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (majorBrand != null) {
|
||||||
|
if (majorBrand in setOf("heic", "heix")) return HEIC
|
||||||
|
if (majorBrand in setOf("mif1", "msf1")) return HEIF
|
||||||
|
if (looksLikeIsoBmffVideo(headerBytes)) return MP4
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromInputStream(inputStream: InputStream): FileType {
|
fun fromInputStream(inputStream: InputStream): FileType {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user