commit 1d0456e8a0c55e883f28a9c758f7cda5dfaf99b4
parent b610825a227d54ae9f5f20396962d76c1e6c2ac7
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat,  6 Apr 2024 15:34:18 +0200

chore(translation): new strings

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt | 2++
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt | 20+++++++++++---------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/TasksRoot.kt | 30+++++++++++++++---------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt | 92++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeLogs.kt | 27+++++++++++++--------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeRoot.kt | 8++++----
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt | 27+++++++++++++++------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/LoggedStories.kt | 10+++++-----
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/ManageScope.kt | 27+++++++++++++--------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/MessagingPreview.kt | 46++++++++++++++++++----------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/SocialRoot.kt | 4++--
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt | 4++--
Mcommon/src/main/assets/lang/en_US.json | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
13 files changed, 238 insertions(+), 154 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt @@ -78,6 +78,8 @@ class Routes( lateinit var routeInfo: RouteInfo lateinit var routes: Routes + val translation by lazy { context.translation.getCategory("manager.sections.${routeInfo.key.substringBefore("/")}")} + private fun replaceArguments(id: String, args: Map<String, String>) = args.takeIf { it.isNotEmpty() }?.let { args.entries.fold(id) { acc, (key, value) -> acc.replace("{$key}", value) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt @@ -126,7 +126,7 @@ class LoggerHistoryRoot : Routes.Route() { LaunchedEffect(Unit, message) { runCatching { decodeMessage(message) { senderId, contentType, messageReader, attachments -> - val senderUsername = senderId?.let { context.modDatabase.getFriendInfo(it)?.mutableUsername } ?: "unknown sender" + val senderUsername = senderId?.let { context.modDatabase.getFriendInfo(it)?.mutableUsername } ?: translation["unknown_sender"] @Composable fun ContentHeader() { @@ -134,7 +134,7 @@ class LoggerHistoryRoot : Routes.Route() { } if (contentType == ContentType.CHAT) { - val content = messageReader.getString(2, 1) ?: "[empty chat message]" + val content = messageReader.getString(2, 1) ?: "[${translation["empty_message"]}]" contentView = { Column { Text(content, modifier = Modifier @@ -166,7 +166,7 @@ class LoggerHistoryRoot : Routes.Route() { downloadAttachment(message.timestamp, attachment) }.onFailure { context.log.error("Failed to download attachment", it) - context.shortToast("Failed to download attachment") + context.shortToast(translation["download_attachment_failed_toast"]) } } }) { @@ -175,7 +175,7 @@ class LoggerHistoryRoot : Routes.Route() { contentDescription = "Download", modifier = Modifier.padding(end = 4.dp) ) - Text("Attachment ${index + 1}") + Text(translation.format("chat_attachment", "index" to (index + 1).toString())) } } } @@ -186,7 +186,7 @@ class LoggerHistoryRoot : Routes.Route() { }.onFailure { context.log.error("Failed to parse message", it) contentView = { - Text("[Failed to parse message]") + Text("[${translation["message_parse_failed"]}]") } } } @@ -212,8 +212,10 @@ class LoggerHistoryRoot : Routes.Route() { ) { fun formatConversationId(conversationId: String?): String? { if (conversationId == null) return null - return context.modDatabase.getGroupInfo(conversationId)?.name?.let { "Group $it" } ?: context.modDatabase.findFriend(conversationId)?.let { - "Friend " + (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername) + return context.modDatabase.getGroupInfo(conversationId)?.name?.let { + translation.format("list_group_format", "name" to it) + } ?: context.modDatabase.findFriend(conversationId)?.let { + translation.format("list_friend_format", "name" to (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername)) } ?: conversationId } @@ -257,7 +259,7 @@ class LoggerHistoryRoot : Routes.Route() { verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp), ) { - Text("Reverse order") + Text(translation["reverse_order_checkbox"]) Checkbox(checked = reverseOrder, onCheckedChange = { reverseOrder = it }) @@ -275,7 +277,7 @@ class LoggerHistoryRoot : Routes.Route() { item { if (selectedConversation != null) { if (hasReachedEnd) { - Text("No more messages", modifier = Modifier + Text(translation["no_more_messages"], modifier = Modifier .padding(8.dp) .fillMaxWidth(), textAlign = TextAlign.Center) } else { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/TasksRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/TasksRoot.kt @@ -8,8 +8,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons - import androidx.compose.material.icons.automirrored.filled.OpenInNew - import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.automirrored.filled.OpenInNew +import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -20,8 +20,8 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.Lifecycle - import androidx.navigation.NavBackStackEntry - import kotlinx.coroutines.CoroutineScope +import androidx.navigation.NavBackStackEntry +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.rhunk.snapenhance.bridge.DownloadCallback @@ -36,8 +36,8 @@ import me.rhunk.snapenhance.task.PendingTaskListener import me.rhunk.snapenhance.task.Task import me.rhunk.snapenhance.task.TaskStatus import me.rhunk.snapenhance.task.TaskType - import me.rhunk.snapenhance.ui.manager.Routes - import me.rhunk.snapenhance.ui.util.OnLifecycleEvent +import me.rhunk.snapenhance.ui.manager.Routes +import me.rhunk.snapenhance.ui.util.OnLifecycleEvent import java.io.File import java.util.UUID import kotlin.math.absoluteValue @@ -99,7 +99,7 @@ class TasksRoot : Routes.Route() { } runCatching { - context.shortToast("Merging ${filesToMerge.size} files") + context.shortToast(translation.format("merge_files_toast", "count" to filesToMerge.size.toString())) FFMpegProcessor.newFFMpegProcessor(context, pendingTask).execute( FFMpegProcessor.Request(FFMpegProcessor.Action.MERGE_MEDIA, filesToMerge.map { it.absolutePath }, mergedFile) ) @@ -177,15 +177,15 @@ class TasksRoot : Routes.Route() { onDismissRequest = { showConfirmDialog = false }, title = { if (taskSelection.isNotEmpty()) { - Text("Remove ${taskSelection.size} tasks?") + Text(translation.format("remove_selected_tasks_confirm", "count" to taskSelection.size.toString())) } else { - Text("Remove all tasks?") + Text(translation["remove_all_tasks_confirm"]) } }, text = { Column { if (taskSelection.isNotEmpty()) { - Text("Are you sure you want to remove selected tasks?") + Text(translation["remove_selected_tasks_title"]) Row ( modifier = Modifier.padding(top = 10.dp).fillMaxWidth().clickable { alsoDeleteFiles = !alsoDeleteFiles @@ -196,10 +196,10 @@ class TasksRoot : Routes.Route() { Checkbox(checked = alsoDeleteFiles, onCheckedChange = { alsoDeleteFiles = it }) - Text("Also delete files") + Text(translation["delete_files_option"]) } } else { - Text("Are you sure you want to remove all tasks?") + Text(translation["remove_all_tasks_title"]) } } }, @@ -233,7 +233,7 @@ class TasksRoot : Routes.Route() { } } ) { - Text("Yes") + Text(context.translation["button.positive"]) } }, dismissButton = { @@ -242,7 +242,7 @@ class TasksRoot : Routes.Route() { showConfirmDialog = false } ) { - Text("No") + Text(context.translation["button.negative"]) } } ) @@ -429,7 +429,7 @@ class TasksRoot : Routes.Route() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - context.translation["manager.sections.tasks.no_tasks"].let { + translation["no_tasks"].let { Icon(Icons.Filled.CheckCircle, contentDescription = it, tint = MaterialTheme.colorScheme.primary) Text(it, style = MaterialTheme.typography.bodyLarge) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt @@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -30,11 +29,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.Lifecycle import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -53,7 +54,6 @@ class FeaturesRoot : Routes.Route() { } private var activityLauncherHelper: ActivityLauncherHelper? = null - private lateinit var rememberScaffoldState: BottomSheetScaffoldState private val allContainers by lazy { val containers = mutableMapOf<String, PropertyPair<*>>() @@ -444,48 +444,62 @@ class FeaturesRoot : Routes.Route() { var showResetConfirmationDialog by remember { mutableStateOf(false) } if (showResetConfirmationDialog) { - Dialog(onDismissRequest = { showResetConfirmationDialog = false }) { - alertDialogs.ConfirmDialog( - title = "Reset config", - message = "Are you sure you want to reset the config?", - onConfirm = { - context.config.reset() - context.shortToast("Config successfully reset!") - }, - onDismiss = { showResetConfirmationDialog = false } - ) - } + AlertDialog( + title = { Text(text = context.translation["manager.dialogs.reset_config.title"]) }, + text = { Text(text = context.translation["manager.dialogs.reset_config.content"]) }, + onDismissRequest = { showResetConfirmationDialog = false }, + confirmButton = { + Button( + onClick = { + context.config.reset() + context.shortToast(context.translation["manager.dialogs.reset_config.success_toast"]) + showResetConfirmationDialog = false + } + ) { + Text(text = context.translation["button.positive"]) + } + }, + dismissButton = { + Button( + onClick = { + showResetConfirmationDialog = false + } + ) { + Text(text = context.translation["button.negative"]) + } + } + ) } val actions = remember { mapOf( - "Export" to { + translation["export_option"] to { activityLauncher { saveFile("config.json", "application/json") { uri -> context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use { context.config.writeConfig() context.config.exportToString().byteInputStream().copyTo(it) - context.shortToast("Config exported successfully!") + context.shortToast(translation["config_export_success_toast"]) } } } }, - "Import" to { + translation["import_option"] to { activityLauncher { openFile("application/json") { uri -> context.androidContext.contentResolver.openInputStream(Uri.parse(uri))?.use { runCatching { context.config.loadFromString(it.readBytes().toString(Charsets.UTF_8)) }.onFailure { - context.longToast("Failed to import config ${it.message}") + context.longToast(translation.format("config_import_failure_toast", "error" to it.message.toString())) return@use } - context.shortToast("Config successfully loaded!") + context.shortToast(translation["config_import_success_toast"]) } } } }, - "Reset" to { showResetConfirmationDialog = true } + translation["reset_option"] to { showResetConfirmationDialog = true } ) } @@ -519,9 +533,7 @@ class FeaturesRoot : Routes.Route() { private fun PropertiesView( properties: List<PropertyPair<*>> ) { - rememberScaffoldState = rememberBottomSheetScaffoldState() Scaffold( - snackbarHost = { SnackbarHost(rememberScaffoldState.snackbarHostState) }, modifier = Modifier.fillMaxSize(), content = { innerPadding -> LazyColumn( @@ -541,23 +553,23 @@ class FeaturesRoot : Routes.Route() { } override val floatingActionButton: @Composable () -> Unit = { - val scope = rememberCoroutineScope() - FloatingActionButton( - onClick = { + fun saveConfig() { + context.coroutineScope.launch(Dispatchers.IO) { 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 - ) + context.log.verbose("saved config!") + } + } + + OnLifecycleEvent { _, event -> + if (event == Lifecycle.Event.ON_PAUSE || event == Lifecycle.Event.ON_STOP) { + saveConfig() + } + } + + DisposableEffect(Unit) { + onDispose { + saveConfig() + } } } @@ -566,10 +578,8 @@ class FeaturesRoot : Routes.Route() { private fun Container( configContainer: ConfigContainer ) { - val properties = remember { + PropertiesView(remember { configContainer.properties.map { PropertyPair(it.key, it.value) } - } - - PropertiesView(properties) + }) } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeLogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeLogs.kt @@ -68,28 +68,27 @@ class HomeLogs : Routes.Route() { navigate() showDropDown = false }, text = { - Text( - text = context.translation["manager.sections.home.logs.clear_logs_button"] - ) + Text(translation["clear_logs_button"]) }) DropdownMenuItem(onClick = { activityLauncherHelper.saveFile("snapenhance-logs-${System.currentTimeMillis()}.zip", "application/zip") { uri -> - context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use { - runCatching { - context.log.exportLogsToZip(it) - context.longToast("Saved logs to $uri") - }.onFailure { - context.longToast("Failed to save logs to $uri!") - context.log.error("Failed to save logs to $uri!", it) + context.coroutineScope.launch { + context.shortToast(translation["saving_logs_toast"]) + context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use { + runCatching { + context.log.exportLogsToZip(it) + context.longToast(translation["saved_logs_success_toast"]) + }.onFailure { + context.longToast(translation["saved_logs_failure_toast"]) + context.log.error("Failed to save logs to $uri!", it) + } } } } showDropDown = false }, text = { - Text( - text = context.translation["manager.sections.home.logs.export_logs_button"] - ) + Text(translation["export_logs_button"]) }) } } @@ -141,7 +140,7 @@ class HomeLogs : Routes.Route() { item { if (lineCount == 0 && logReader != null) { Text( - text = "No logs found!", + text = translation["no_logs_hint"], modifier = Modifier.padding(16.dp), fontSize = 12.sp, fontWeight = FontWeight.Light diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeRoot.kt @@ -198,9 +198,9 @@ class HomeRoot : Routes.Route() { } ) } - Spacer(modifier = Modifier.height(20.dp)) if (latestUpdate != null) { + Spacer(modifier = Modifier.height(20.dp)) OutlinedCard( modifier = Modifier .padding(all = cardMargin) @@ -218,13 +218,13 @@ class HomeRoot : Routes.Route() { ) { Column { Text( - text = "SnapEnhance Update", + text = translation["update_title"], fontSize = 14.sp, fontWeight = FontWeight.Bold, ) Text( fontSize = 12.sp, - text = "Version ${latestUpdate?.versionName} is available!", + text = translation.format("update_content", "version" to (latestUpdate?.versionName ?: "unknown")), lineHeight = 20.sp ) } @@ -235,7 +235,7 @@ class HomeRoot : Routes.Route() { } ) }, modifier = Modifier.height(40.dp)) { - Text(text = "Download") + Text(text = translation["update_button"]) } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt @@ -84,7 +84,7 @@ class HomeSettings : Routes.Route() { if (requireConfirmation && confirmationDialog) { Dialog(onDismissRequest = { confirmationDialog = false }) { - dialogs.ConfirmDialog(title = "Are you sure?", onConfirm = { + dialogs.ConfirmDialog(title = context.translation["manager.dialogs.action_confirm.title"], onConfirm = { action() confirmationDialog = false }, onDismiss = { @@ -148,7 +148,7 @@ class HomeSettings : Routes.Route() { .fillMaxSize() .verticalScroll(ScrollState(0)) ) { - RowTitle(title = "Actions") + RowTitle(title = translation["actions_title"]) EnumAction.entries.forEach { enumAction -> RowAction(key = enumAction.key) { launchActionIntent(enumAction) @@ -160,7 +160,7 @@ class HomeSettings : Routes.Route() { RowAction(key = "change_language") { context.checkForRequirements(Requirements.LANGUAGE) } - RowTitle(title = "Message Logger") + RowTitle(title = translation["message_logger_title"]) ShiftedRow { Column( verticalArrangement = Arrangement.spacedBy(4.dp), @@ -184,8 +184,11 @@ class HomeSettings : Routes.Route() { modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp), ) { - Text(text = "$storedMessagesCount messages") - Text(text = "$storedStoriesCount stories") + Text( + translation.format("message_logger_summary", + "messageCount" to storedMessagesCount.toString(), + "storyCount" to storedStoriesCount.toString() + ), maxLines = 2) } Button(onClick = { runCatching { @@ -201,7 +204,7 @@ class HomeSettings : Routes.Route() { context.longToast("Failed to export database! ${it.localizedMessage}") } }) { - Text(text = "Export") + Text(text = translation["export_button"]) } Button(onClick = { runCatching { @@ -212,10 +215,10 @@ class HomeSettings : Routes.Route() { context.log.error("Failed to clear messages", it) context.longToast("Failed to clear messages! ${it.localizedMessage}") }.onSuccess { - context.shortToast("Done!") + context.shortToast(translation["success_toast"]) } }) { - Text(text = "Clear") + Text(text = translation["clear_button"]) } } OutlinedButton( @@ -226,12 +229,12 @@ class HomeSettings : Routes.Route() { routes.loggerHistory.navigate() } ) { - Text(text = "View Message History") + Text(translation["view_logger_history_button"]) } } } - RowTitle(title = "Debug") + RowTitle(title = translation["debug_title"]) Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, @@ -275,10 +278,10 @@ class HomeSettings : Routes.Route() { context.log.error("Failed to clear file", it) context.longToast("Failed to clear file! ${it.localizedMessage}") }.onSuccess { - context.shortToast("Done!") + context.shortToast(translation["success_toast"]) } }) { - Text(text = "Clear File") + Text(translation["clear_button"]) } } ShiftedRow { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/LoggedStories.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/LoggedStories.kt @@ -148,7 +148,7 @@ class LoggedStories : Routes.Route() { } } }) { - Text(text = "Open") + Text(text = context.translation["button.open"]) } Button(onClick = { @@ -160,7 +160,7 @@ class LoggedStories : Routes.Route() { ) ) }) { - Text(text = "Download") + Text(text = context.translation["button.download"]) } if (remember { @@ -180,7 +180,7 @@ class LoggedStories : Routes.Route() { ) ) }) { - Text(text = "Save from cache") + Text(text = translation["save_from_cache_button"]) } } } @@ -190,7 +190,7 @@ class LoggedStories : Routes.Route() { } if (stories.isEmpty()) { - Text(text = "No stories found", Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Text(text = translation["no_stories"], Modifier.fillMaxWidth(), textAlign = TextAlign.Center) } LazyVerticalGrid( @@ -212,7 +212,7 @@ class LoggedStories : Routes.Route() { verticalArrangement = Arrangement.Center, ) { if (hasFailed) { - Text(text = "Failed to load", Modifier.padding(8.dp), fontSize = 10.sp) + Text(text = translation["story_failed_to_load"], Modifier.padding(8.dp), fontSize = 10.sp) } else { Image( painter = rememberAsyncImagePainter( diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/ManageScope.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/ManageScope.kt @@ -29,7 +29,6 @@ import kotlin.io.encoding.ExperimentalEncodingApi class ManageScope: Routes.Route() { private val dialogs by lazy { AlertDialogs(context.translation) } - private val translation by lazy { context.translation.getCategory("manager.sections.social") } private fun deleteScope(scope: SocialScope, id: String, coroutineScope: CoroutineScope) { when (scope) { @@ -56,7 +55,7 @@ class ManageScope: Routes.Route() { deleteConfirmDialog = false }) { remember { AlertDialogs(context.translation) }.ConfirmDialog( - title = "Are you sure you want to delete this ${scope.key.lowercase()}?", + title = translation.format("delete_scope_confirm_dialog_title", "scope" to context.translation["scopes.${scope.key}"]), onDismiss = { deleteConfirmDialog = false }, onConfirm = { deleteScope(scope, id, coroutineScope); deleteConfirmDialog = false @@ -94,7 +93,6 @@ class ManageScope: Routes.Route() { SectionTitle(translation["rules_title"]) ContentCard { - //manager anti features etc MessagingRuleType.entries.forEach { ruleType -> var ruleEnabled by remember { mutableStateOf(rules.any { it.key == ruleType.key }) @@ -110,14 +108,17 @@ class ManageScope: Routes.Route() { text = if (ruleType.listMode && ruleState != null) { context.translation["rules.properties.${ruleType.key}.options.${ruleState.key}"] } else context.translation["rules.properties.${ruleType.key}.name"], - modifier = Modifier.weight(1f).padding(start = 5.dp, end = 5.dp) + modifier = Modifier + .weight(1f) + .padding(start = 5.dp, end = 5.dp) ) Switch(checked = ruleEnabled, enabled = if (ruleType.listMode) ruleState != null else true, onCheckedChange = { context.modDatabase.setRule(id, ruleType.key, it) ruleEnabled = it - }) + } + ) } } } @@ -155,8 +156,7 @@ class ManageScope: Routes.Route() { ) } - //need to display all units? - private fun computeStreakETA(timestamp: Long): String { + private fun computeStreakETA(timestamp: Long): String? { val now = System.currentTimeMillis() val stringBuilder = StringBuilder() val diff = timestamp - now @@ -180,7 +180,7 @@ class ManageScope: Routes.Route() { stringBuilder.append("$seconds seconds ") return stringBuilder.toString() } - return "Expired" + return null } @OptIn(ExperimentalEncodingApi::class) @@ -234,7 +234,7 @@ class ManageScope: Routes.Route() { put("id", id) } }) { - Text("Show Logged Stories") + Text(translation["logged_stories_button"]) } } @@ -259,10 +259,11 @@ class ManageScope: Routes.Route() { ), maxLines = 1 ) Text( - text = translation.format( + text = computeStreakETA(streaks.expirationTimestamp)?.let { translation.format( "streaks_expiration_text", - "eta" to computeStreakETA(streaks.expirationTimestamp) - ), maxLines = 1 + "eta" to it + ) } ?: translation["streaks_expiration_text_expired"], + maxLines = 1 ) } Row( @@ -282,7 +283,6 @@ class ManageScope: Routes.Route() { } } Spacer(modifier = Modifier.height(16.dp)) - // e2ee section if (context.config.root.experimental.e2eEncryption.globalState == true) { SectionTitle(translation["e2ee_title"]) @@ -362,7 +362,6 @@ class ManageScope: Routes.Route() { return } - Column( modifier = Modifier .padding(10.dp) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/MessagingPreview.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/MessagingPreview.kt @@ -47,7 +47,6 @@ class MessagingPreview: Routes.Route() { private lateinit var messagingBridge: MessagingBridge private lateinit var previewScrollState: LazyListState - private val myUserId by lazy { messagingBridge.myUserId } private val contentTypeTranslation by lazy { context.translation.getCategory("content_type") } private var messages = mutableStateListOf<Message>() @@ -117,7 +116,7 @@ class MessagingPreview: Routes.Route() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(5.dp) ) { - Text("Choose content types to process") + Text(context.translation["manager.dialogs.messaging_action.title"]) Spacer(modifier = Modifier.height(5.dp)) availableTypes.forEach { contentType -> Row( @@ -135,7 +134,7 @@ class MessagingPreview: Routes.Route() { enabled = !selectAllState, onCheckedChange = { toggleContentType(contentType) } ) - Text(text = contentType.toString()) + Text(text = contentTypeTranslation[contentType.name]) } } Row( @@ -148,7 +147,7 @@ class MessagingPreview: Routes.Route() { Switch(checked = selectAllState, onCheckedChange = { selectAllState = it }) - Text(text = "Select all") + Text(text = context.translation["manager.dialogs.messaging_action.select_all_button"]) } Row( modifier = Modifier @@ -156,13 +155,13 @@ class MessagingPreview: Routes.Route() { horizontalArrangement = Arrangement.SpaceEvenly, ) { Button(onClick = { onDismiss() }) { - Text("Cancel") + Text(context.translation["button.cancel"]) } Button(onClick = { onChoose(if (selectAllState) ContentType.entries.toTypedArray() else selectedTypes.toTypedArray()) }) { - Text("Continue") + Text(context.translation["button.ok"]) } } } @@ -286,28 +285,28 @@ class MessagingPreview: Routes.Route() { shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(50.dp)) ) { DropdownMenu( - expanded = taskSelectionDropdown, onDismissRequest = { taskSelectionDropdown = false } + expanded = taskSelectionDropdown && messages.isNotEmpty(), onDismissRequest = { taskSelectionDropdown = false } ) { val hasSelection = selectedMessages.isNotEmpty() - ActionButton(text = if (hasSelection) "Save selection" else "Save all", icon = Icons.Rounded.BookmarkAdded) { + ActionButton(text = translation[if (hasSelection) "save_selection_option" else "save_all_option"], icon = Icons.Rounded.BookmarkAdded) { launchMessagingTask(MessagingTaskType.SAVE) if (hasSelection) runCurrentTask() else selectConstraintsDialog = true } - ActionButton(text = if (hasSelection) "Unsave selection" else "Unsave all", icon = Icons.Rounded.BookmarkBorder) { + ActionButton(text = translation[if (hasSelection) "unsave_selection_option" else "unsave_all_option"], icon = Icons.Rounded.BookmarkBorder) { launchMessagingTask(MessagingTaskType.UNSAVE) if (hasSelection) runCurrentTask() else selectConstraintsDialog = true } - ActionButton(text = if (hasSelection) "Mark selected Snap as seen" else "Mark all Snaps as seen", icon = Icons.Rounded.RemoveRedEye) { + ActionButton(text = translation[if (hasSelection) "mark_selection_as_seen_option" else "mark_all_as_seen_option"], icon = Icons.Rounded.RemoveRedEye) { launchMessagingTask(MessagingTaskType.READ, listOf( - MessagingConstraints.NO_USER_ID(myUserId), + MessagingConstraints.NO_USER_ID(messagingBridge.myUserId), MessagingConstraints.CONTENT_TYPE(arrayOf(ContentType.SNAP)) )) runCurrentTask() } - ActionButton(text = if (hasSelection) "Delete selected" else "Delete all", icon = Icons.Rounded.DeleteForever) { - launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(myUserId))) { message -> + ActionButton(text = translation[if (hasSelection) "delete_selection_option" else "delete_all_option"], icon = Icons.Rounded.DeleteForever) { + launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(messagingBridge.myUserId))) { message -> coroutineScope.launch { message.contentType = ContentType.STATUS.id } @@ -377,7 +376,7 @@ class MessagingPreview: Routes.Route() { .padding(40.dp), horizontalArrangement = Arrangement.Center ) { - Text("No messages") + Text(translation["no_message_hint"]) } } Spacer(modifier = Modifier.height(20.dp)) @@ -428,12 +427,7 @@ class MessagingPreview: Routes.Route() { conversationId!!, 20, lastMessageId - )?.reversed() - - if (queriedMessages == null) { - context.shortToast("Failed to fetch messages") - return@cs - } + )?.reversed() ?: throw IllegalStateException("Failed to fetch messages. Bridge returned null") withContext(Dispatchers.Main) { messages.addAll(queriedMessages) @@ -441,7 +435,7 @@ class MessagingPreview: Routes.Route() { } }.onFailure { context.log.error("Failed to fetch messages", it) - context.shortToast("Failed to fetch messages: ${it.message}") + context.shortToast(translation["message_fetch_failed"]) } } } @@ -451,11 +445,7 @@ class MessagingPreview: Routes.Route() { runCatching { messagingBridge = context.bridgeService!!.messagingBridge!! - conversationId = if (scope == SocialScope.FRIEND) messagingBridge.getOneToOneConversationId(scopeId) else scopeId - if (conversationId == null) { - context.longToast("Failed to fetch conversation id") - return - } + conversationId = (if (scope == SocialScope.FRIEND) messagingBridge.getOneToOneConversationId(scopeId) else scopeId) ?: throw IllegalStateException("Failed to get conversation id") if (runCatching { !messagingBridge.isSessionStarted }.getOrDefault(true)) { context.androidContext.packageManager.getLaunchIntentForPackage( Constants.SNAPCHAT_PACKAGE_NAME @@ -474,7 +464,7 @@ class MessagingPreview: Routes.Route() { } fetchNewMessages() }.onFailure { - context.longToast("Failed to initialize messaging bridge") + context.longToast(translation["bridge_init_failed"]) context.log.error("Failed to initialize messaging bridge", it) } } @@ -511,7 +501,7 @@ class MessagingPreview: Routes.Route() { .fillMaxSize() ) { if (hasBridgeError) { - Text("Failed to connect to Snapchat through bridge service") + Text(translation["bridge_connection_failed"]) } if (!isBridgeConnected && !hasBridgeError) { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/SocialRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/SocialRoot.kt @@ -164,8 +164,8 @@ class SocialRoot : Routes.Route() { else MaterialTheme.colorScheme.primary ) Text( - text = context.translation.format( - "manager.sections.social.streaks_expiration_short", + text = translation.format( + "streaks_expiration_short", "hours" to (((streaks.expirationTimestamp - System.currentTimeMillis()) / 3600000).toInt().takeIf { it > 0 } ?: 0) .toString() ), diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt @@ -44,9 +44,9 @@ class AlertDialogs( @Composable fun DefaultDialogCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) { Card( - shape = MaterialTheme.shapes.medium, + shape = MaterialTheme.shapes.large, modifier = Modifier - .padding(10.dp, 5.dp, 10.dp, 10.dp) + .padding(16.dp) .then(modifier), ) { Column( diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -21,6 +21,11 @@ } }, + "scopes": { + "friend": "Friend", + "group": "Group" + }, + "manager": { "routes": { "tasks": "Tasks", @@ -37,27 +42,92 @@ }, "sections": { "home": { - "logs": { - "clear_logs_button": "Clear Logs", - "export_logs_button": "Export Logs" - } + "update_title": "SnapEnhance Update", + "update_content": "Version {version} is available!", + "update_button": "Download" + }, + "home_logs": { + "no_logs_hint": "No logs available", + "clear_logs_button": "Clear Logs", + "export_logs_button": "Export Logs", + "saving_logs_toast": "Saving logs, this may take a while ...", + "saved_logs_success_toast": "Logs saved successfully", + "saved_logs_failure_toast": "Failed to save logs" + }, + "home_settings": { + "actions_title": "Actions", + "message_logger_title": "Message Logger", + "debug_title": "Debug", + "success_toast": "Done!", + "message_logger_summary": "{messageCount} messages\n{storyCount} stories", + "export_button": "Export", + "clear_button": "Clear", + "view_logger_history_button": "View Logger History" }, "tasks": { - "no_tasks": "No tasks" + "no_tasks": "No tasks", + "merge_files_toast": "Merging {count} files", + "remove_selected_tasks_title": "Are you sure you want to remove selected tasks?", + "remove_all_tasks_title": "Are you sure you want to remove all tasks?", + "delete_files_option": "Also delete files", + "remove_selected_tasks_confirm": "Remove {count} tasks?", + "remove_all_tasks_confirm": "Remove all tasks?" }, "features": { - "disabled": "Disabled" + "disabled": "Disabled", + "export_option": "Export", + "import_option": "Import", + "reset_option": "Reset", + "config_export_success_toast": "Config exported successfully", + "config_import_success_toast": "Config imported successfully", + "config_import_failure_toast": "Failed to import config {error}", + "saved_config_snackbar": "Config saved" }, "social": { + "streaks_expiration_short": "{hours}h" + }, + "manage_scope": { + "logged_stories_button": "Show Logged Stories", "e2ee_title": "End-to-End Encryption", "rules_title": "Rules", "participants_text": "{count} participants", "not_found": "Not found", "streaks_title": "Streaks", "streaks_length_text": "Length: {length}", - "streaks_expiration_short": "{hours}h", "streaks_expiration_text": "Expires in {eta}", - "reminder_button": "Set Reminder" + "streaks_expiration_text_expired": "Expired", + "reminder_button": "Set Reminder", + "delete_scope_confirm_dialog_title": "Are you sure you want to delete a {scope}?" + }, + "logged_stories": { + "story_failed_to_load": "Failed to load", + "no_stories": "No stories found", + "save_from_cache_button": "Save from Cache" + }, + "messaging_preview": { + "bridge_connection_failed": "Failed to connect to Snapchat through bridge service", + "bridge_init_failed": "Failed to initialize messaging bridge", + "message_fetch_failed": "Failed to fetch messages", + "no_message_hint": "No message", + "save_selection_option": "Save Selection", + "save_all_option": "Save All", + "unsave_selection_option": "Unsave Selection", + "unsave_all_option": "Unsave All", + "mark_selection_as_seen_option": "Mark selected Snap as seen", + "mark_all_as_seen_option": "Mark all Snaps as seen", + "delete_selection_option": "Delete Selection", + "delete_all_option": "Delete All" + }, + "logger_history": { + "list_friend_format": "Friend {name}", + "list_group_format": "Group {name}", + "no_more_messages": "No more messages", + "reverse_order_checkbox": "Reverse Order", + "chat_attachment": "Attachment {index}", + "empty_message": "Empty Chat Message", + "message_parse_failed": "Failed to parse message", + "unknown_sender": "Unknown Sender", + "download_attachment_failed_toast": "Failed to download attachment" } }, "dialogs": { @@ -71,6 +141,15 @@ "scripting_warning": { "title": "Warning", "content": "SnapEnhance includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorized or unverified modules may pose security risks to your system." + }, + "reset_config": { + "title": "Reset config", + "content": "Are you sure you want to reset the config?", + "success_toast": "Config reset successfully" + }, + "messaging_action": { + "title": "Choose content types to process", + "select_all_button": "Select All" } } },