diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0d39986c..a71d84ea 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,10 +34,10 @@
tools:ignore="ExportedService">
-
-
+
diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json
index f1f58cde..0f38faa4 100644
--- a/app/src/main/assets/lang/en_US.json
+++ b/app/src/main/assets/lang/en_US.json
@@ -161,7 +161,7 @@
},
"conversation_preview": {
- "streak_expiration": "expires in %s days %s hours %s minutes",
+ "streak_expiration": "expires in {day} days {hour} hours {minute} minutes",
"title": "Preview",
"unknown_user": "Unknown User"
},
@@ -199,5 +199,44 @@
"finished": "Done! You now can close this dialog.",
"no_messages_found": "No messages found!",
"exporting_message": "Exporting {conversation}..."
+ },
+
+ "download_manager_activity": {
+ "remove_all_title": "Remove all Downloads",
+ "remove_all_text": "Are you sure you want to do this?",
+ "remove_all": "Remove All",
+ "no_downloads": "No downloads",
+ "cancel": "Cancel",
+ "file_not_found_toast": "File does not exist!",
+ "category": {
+ "all_category": "All",
+ "pending_category": "Pending",
+ "snap_category": "Snaps",
+ "story_category": "Stories",
+ "spotlight_category": "Spotlight"
+ },
+ "settings": "Settings",
+ "button": {
+ "positive": "Yes",
+ "negative": "No",
+ "cancel": "Cancel",
+ "open": "Open"
+ },
+ "settings_page": {
+ "clear_file_title": "Clear {file_name} file",
+ "clear_file_confirmation": "Are you sure you want to clear the {file_name} file?",
+ "clear_cache_title": "Clear Cache",
+ "reset_all_title": "Reset all settings",
+ "reset_all_confirmation": "Are you sure you want to reset all settings?",
+ "success_toast": "Success!"
+ }
+ },
+ "download_manager_receiver": {
+ "saved_toast": "Saved to {path}",
+ "download_toast": "Downloading {path}...",
+ "processing_toast": "Processing {path}...",
+ "failed_generic_toast": "Failed to download",
+ "failed_processing_toast": "Failed to process {error}",
+ "failed_gallery_toast": "Failed to save to gallery {error}"
}
}
\ No newline at end of file
diff --git a/app/src/main/assets/lang/fr_FR.json b/app/src/main/assets/lang/fr_FR.json
index b8c512e6..af4d5270 100644
--- a/app/src/main/assets/lang/fr_FR.json
+++ b/app/src/main/assets/lang/fr_FR.json
@@ -159,7 +159,7 @@
"username": "Nom d'utilisateur",
"display_name": "Nom d'affichage",
"added_date": "Date d'ajout",
- "birthday": "Anniversaire : {month} {day}"
+ "birthday": "Anniversaire : {day} {month}"
},
"auto_updater": {
"no_update_available": "Aucune mise à jour disponible !",
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt
index d7c4b067..99710231 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt
@@ -10,7 +10,9 @@ import android.os.Process
import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.GsonBuilder
+import kotlinx.coroutines.asCoroutineDispatcher
import me.rhunk.snapenhance.bridge.AbstractBridgeClient
+import me.rhunk.snapenhance.bridge.TranslationWrapper
import me.rhunk.snapenhance.data.MessageSender
import me.rhunk.snapenhance.database.DatabaseAccess
import me.rhunk.snapenhance.features.Feature
@@ -18,7 +20,6 @@ import me.rhunk.snapenhance.manager.impl.ActionManager
import me.rhunk.snapenhance.manager.impl.ConfigManager
import me.rhunk.snapenhance.manager.impl.FeatureManager
import me.rhunk.snapenhance.manager.impl.MappingManager
-import me.rhunk.snapenhance.manager.impl.TranslationManager
import me.rhunk.snapenhance.util.download.DownloadServer
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -28,13 +29,17 @@ import kotlin.system.exitProcess
class ModContext {
private val executorService: ExecutorService = Executors.newCachedThreadPool()
+ val coroutineDispatcher by lazy {
+ executorService.asCoroutineDispatcher()
+ }
+
lateinit var androidContext: Context
var mainActivity: Activity? = null
lateinit var bridgeClient: AbstractBridgeClient
val gson: Gson = GsonBuilder().create()
- val translation = TranslationManager(this)
+ val translation = TranslationWrapper()
val features = FeatureManager(this)
val mappings = MappingManager(this)
val config = ConfigManager(this)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/SharedContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/SharedContext.kt
new file mode 100644
index 00000000..67a793de
--- /dev/null
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/SharedContext.kt
@@ -0,0 +1,26 @@
+package me.rhunk.snapenhance
+
+import android.content.Context
+import me.rhunk.snapenhance.bridge.TranslationWrapper
+import me.rhunk.snapenhance.download.DownloadTaskManager
+
+/**
+ * Used to store objects between activities and receivers
+ */
+object SharedContext {
+ lateinit var downloadTaskManager: DownloadTaskManager
+ lateinit var translation: TranslationWrapper
+
+ fun ensureInitialized(context: Context) {
+ if (!this::downloadTaskManager.isInitialized) {
+ downloadTaskManager = DownloadTaskManager().apply {
+ init(context)
+ }
+ }
+ if (!this::translation.isInitialized) {
+ translation = TranslationWrapper().apply {
+ loadFromContext(context)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt b/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt
index 1ce7a769..0ee62951 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt
@@ -5,6 +5,8 @@ import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Build
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.bridge.AbstractBridgeClient
import me.rhunk.snapenhance.bridge.client.RootBridgeClient
import me.rhunk.snapenhance.bridge.client.ServiceBridgeClient
@@ -39,7 +41,9 @@ class SnapEnhance {
return@start
}
runCatching {
- init()
+ runBlocking {
+ init()
+ }
}.onFailure {
Logger.xposedLog("Failed to initialize", it)
}
@@ -67,10 +71,14 @@ class SnapEnhance {
}
@OptIn(ExperimentalTime::class)
- private fun init() {
+ private suspend fun init() {
+ //load translations in a coroutine to speed up initialization
+ withContext(appContext.coroutineDispatcher) {
+ appContext.translation.loadFromBridge(appContext.bridgeClient)
+ }
+
measureTime {
with(appContext) {
- translation.init()
config.init()
mappings.init()
//if mappings aren't loaded, we can't initialize features
@@ -82,10 +90,15 @@ class SnapEnhance {
}
}
+ @OptIn(ExperimentalTime::class)
private fun onActivityCreate() {
- with(appContext) {
- features.onActivityCreate()
- actionManager.init()
+ measureTime {
+ with(appContext) {
+ features.onActivityCreate()
+ actionManager.init()
+ }
+ }.also { time ->
+ Logger.debug("onActivityCreate in $time")
}
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt
index ac724ce6..c142d1fa 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt
@@ -10,7 +10,7 @@ class CheckForUpdates : AbstractAction("action.check_for_updates", dependsOnProp
runCatching {
val latestVersion = context.feature(AutoUpdater::class).checkForUpdates()
if (latestVersion == null) {
- context.longToast(context.translation.get("auto_updater.no_update_available"))
+ context.longToast(context.translation["auto_updater.no_update_available"])
}
}.onFailure {
context.longToast(it.message ?: "Failed to check for updates")
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt
index 69907369..b3d1d223 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt
@@ -62,7 +62,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
private suspend fun askExportType() = suspendCancellableCoroutine { cont ->
context.runOnUiThread {
AlertDialog.Builder(context.mainActivity)
- .setTitle(context.translation.get("chat_export.select_export_format"))
+ .setTitle(context.translation["chat_export.select_export_format"])
.setItems(ExportFormat.values().map { it.name }.toTypedArray()) { _, which ->
cont.resumeWith(Result.success(ExportFormat.values()[which]))
}
@@ -83,7 +83,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
ContentType.STICKER
)
AlertDialog.Builder(context.mainActivity)
- .setTitle(context.translation.get("chat_export.select_media_type"))
+ .setTitle(context.translation["chat_export.select_media_type"])
.setMultiChoiceItems(contentTypes.map { it.name }.toTypedArray(), BooleanArray(contentTypes.size) { false }) { _, which, isChecked ->
val media = contentTypes[which]
if (isChecked) {
@@ -111,7 +111,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
val selectedConversations = mutableListOf()
AlertDialog.Builder(context.mainActivity)
- .setTitle(context.translation.get("chat_export.select_conversation"))
+ .setTitle(context.translation["chat_export.select_conversation"])
.setMultiChoiceItems(
friendFeedEntries.map { it.feedDisplayName ?: it.friendDisplayName!!.split("|").firstOrNull() }.toTypedArray(),
BooleanArray(friendFeedEntries.size) { false }
@@ -122,13 +122,13 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
selectedConversations.remove(friendFeedEntries[which])
}
}
- .setNegativeButton(context.translation.get("chat_export.dialog_negative_button")) { dialog, _ ->
+ .setNegativeButton(context.translation["chat_export.dialog_negative_button"]) { dialog, _ ->
dialog.dismiss()
}
- .setNeutralButton(context.translation.get("chat_export.dialog_neutral_button")) { _, _ ->
+ .setNeutralButton(context.translation["chat_export.dialog_neutral_button"]) { _, _ ->
exportChatForConversations(friendFeedEntries)
}
- .setPositiveButton(context.translation.get("chat_export.dialog_positive_button")) { _, _ ->
+ .setPositiveButton(context.translation["chat_export.dialog_positive_button"]) { _, _ ->
exportChatForConversations(selectedConversations)
}
.show()
@@ -188,11 +188,11 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
conversationAction(true, conversationId, if (friendFeedInfo.feedDisplayName != null) "USERCREATEDGROUP" else "ONEONONE")
- logDialog(context.translation.get("chat_export.exporting_message").replace("{conversation}", conversationName))
+ logDialog(context.translation.format("chat_export.exporting_message", "conversation" to conversationName))
val foundMessages = fetchMessagesPaginated(conversationId, Long.MAX_VALUE).toMutableList()
var lastMessageId = foundMessages.firstOrNull()?.messageDescriptor?.messageId ?: run {
- logDialog(context.translation.get("chat_export.no_messages_found"))
+ logDialog(context.translation["chat_export.no_messages_found"])
return
}
@@ -211,7 +211,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
"SnapEnhance/conversation_${conversationName}_${System.currentTimeMillis()}.${exportType!!.extension}"
).also { it.parentFile?.mkdirs() }
- logDialog(context.translation.get("chat_export.writing_output"))
+ logDialog(context.translation["chat_export.writing_output"])
MessageExporter(
context = context,
friendFeedInfo = friendFeedInfo,
@@ -222,14 +222,16 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
runCatching {
it.readMessages(foundMessages)
}.onFailure {
- logDialog(context.translation.get("chat_export.export_failed").replace("{conversation}", it.message.toString()))
+ logDialog(context.translation.format("chat_export.export_failed","conversation" to it.message.toString()))
Logger.error(it)
return
}
}.exportTo(exportType!!)
dialogLogs.clear()
- logDialog("\n" + context.translation.get("chat_export.exported_to").replace("{path}", outputFile.absolutePath.toString()) + "\n")
+ logDialog("\n" + context.translation.format("chat_export.exported_to",
+ "path" to outputFile.absolutePath.toString()
+ ) + "\n")
currentActionDialog?.setButton(DialogInterface.BUTTON_POSITIVE, "Open") { _, _ ->
val intent = Intent(Intent.ACTION_VIEW)
@@ -247,16 +249,16 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
val jobs = mutableListOf()
currentActionDialog = AlertDialog.Builder(context.mainActivity)
- .setTitle(context.translation.get("chat_export.exporting_chats"))
+ .setTitle(context.translation["chat_export.exporting_chats"])
.setCancelable(false)
.setMessage("")
- .setNegativeButton(context.translation.get("chat_export.dialog_negative_button")) { dialog, _ ->
+ .setNegativeButton(context.translation["chat_export.dialog_negative_button"]) { dialog, _ ->
jobs.forEach { it.cancel() }
dialog.dismiss()
}
.create()
- val conversationSize = context.translation.get("chat_export.processing_chats").replace("{amount}", conversations.size.toString())
+ val conversationSize = context.translation.format("chat_export.processing_chats", "amount" to conversations.size.toString())
logDialog(conversationSize)
@@ -268,14 +270,14 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
runCatching {
exportFullConversation(conversation)
}.onFailure {
- logDialog(context.translation.get("chat_export.export_fail").replace("{conversation}", conversation.key.toString()))
+ logDialog(context.translation.format("chat_export.export_fail", "conversation" to conversation.key.toString()))
logDialog(it.stackTraceToString())
Logger.xposedLog(it)
}
}.also { jobs.add(it) }
}
jobs.joinAll()
- logDialog(context.translation.get("chat_export.finished"))
+ logDialog(context.translation["chat_export.finished"])
}
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/TranslationWrapper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/TranslationWrapper.kt
new file mode 100644
index 00000000..a7225f0b
--- /dev/null
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/TranslationWrapper.kt
@@ -0,0 +1,96 @@
+package me.rhunk.snapenhance.bridge
+
+import android.content.Context
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import me.rhunk.snapenhance.Logger
+import me.rhunk.snapenhance.data.LocalePair
+import java.util.Locale
+
+
+class TranslationWrapper {
+ companion object {
+ private const val DEFAULT_LOCALE = "en_US"
+
+ fun fetchLocales(context: Context): List {
+ val deviceLocale = Locale.getDefault().toString()
+ val locales = mutableListOf()
+
+ locales.add(LocalePair(DEFAULT_LOCALE, context.resources.assets.open("lang/$DEFAULT_LOCALE.json").bufferedReader().use { it.readText() }))
+
+ if (deviceLocale == DEFAULT_LOCALE) return locales
+
+ val compatibleLocale = context.resources.assets.list("lang")?.firstOrNull { it.startsWith(deviceLocale) }?.substring(0, 5) ?: return locales
+
+ context.resources.assets.open("lang/$compatibleLocale.json").use { inputStream ->
+ locales.add(LocalePair(compatibleLocale, inputStream.bufferedReader().use { it.readText() }))
+ }
+
+ return locales
+ }
+ }
+
+
+ private val translationMap = linkedMapOf()
+ private lateinit var _locale: String
+
+ val locale by lazy {
+ Locale(_locale.substring(0, 2), _locale.substring(3, 5))
+ }
+
+ private fun load(localePair: LocalePair) {
+ if (!::_locale.isInitialized) {
+ _locale = localePair.locale
+ }
+
+ val translations = JsonParser.parseString(localePair.content).asJsonObject
+ if (translations == null || translations.isJsonNull) {
+ return
+ }
+
+ fun scanObject(jsonObject: JsonObject, prefix: String = "") {
+ jsonObject.entrySet().forEach {
+ if (it.value.isJsonPrimitive) {
+ val key = "$prefix${it.key}"
+ translationMap[key] = it.value.asString
+ }
+ if (!it.value.isJsonObject) return@forEach
+ scanObject(it.value.asJsonObject, "$prefix${it.key}.")
+ }
+ }
+
+ scanObject(translations)
+ }
+
+ fun loadFromBridge(bridgeClient: AbstractBridgeClient) {
+ bridgeClient.fetchTranslations().getLocales().forEach {
+ load(it)
+ }
+ }
+
+ fun loadFromContext(context: Context) {
+ fetchLocales(context).forEach {
+ load(it)
+ }
+ }
+
+ operator fun get(key: String): String {
+ return translationMap[key] ?: key.also { Logger.debug("Missing translation for $key") }
+ }
+
+ fun format(key: String, vararg args: Pair): String {
+ return args.fold(get(key)) { acc, pair ->
+ acc.replace("{${pair.first}}", pair.second)
+ }
+ }
+
+ fun getCategory(key: String): TranslationWrapper {
+ return TranslationWrapper().apply {
+ translationMap.putAll(
+ this@TranslationWrapper.translationMap
+ .filterKeys { it.startsWith("$key.") }
+ .mapKeys { it.key.substring(key.length + 1) }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt
index bf700ec3..760705e8 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt
@@ -110,7 +110,7 @@ class RootBridgeClient : AbstractBridgeClient() {
if (langJsonData != null) {
Logger.debug("Fetched translations for $locale")
- return LocaleResult(locale, langJsonData)
+ return LocaleResult(arrayOf(locale), arrayOf(langJsonData.toString(Charsets.UTF_8)))
}
throw Throwable("Failed to fetch translations for $locale")
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt
index ba587e31..310b4c09 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt
@@ -2,18 +2,30 @@ package me.rhunk.snapenhance.bridge.common.impl.locale
import android.os.Bundle
import me.rhunk.snapenhance.bridge.common.BridgeMessage
+import me.rhunk.snapenhance.data.LocalePair
class LocaleResult(
- var locale: String? = null,
- var content: ByteArray? = null
+ private var locales: Array? = null,
+ private var localContentArray: Array? = null
) : BridgeMessage(){
- override fun write(bundle: Bundle) {
- bundle.putString("locale", locale)
- bundle.putByteArray("content", content)
+
+ fun getLocales(): List {
+ val locales = locales ?: return emptyList()
+ val localContentArray = localContentArray ?: return emptyList()
+ return locales.mapIndexed { index, locale ->
+ LocalePair(locale, localContentArray[index])
+ }.asReversed()
}
+
+ override fun write(bundle: Bundle) {
+ bundle.putStringArray("locales", locales)
+ bundle.putSerializable("localContentArray", localContentArray)
+ }
+
+ @Suppress("UNCHECKED_CAST", "DEPRECATION")
override fun read(bundle: Bundle) {
- locale = bundle.getString("locale")
- content = bundle.getByteArray("content")
+ locales = bundle.getStringArray("locales")
+ localContentArray = bundle.getSerializable("localContentArray") as? Array
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt
index f72272e1..478bb2a5 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt
@@ -8,6 +8,7 @@ import android.net.Uri
import android.os.*
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.bridge.MessageLoggerWrapper
+import me.rhunk.snapenhance.bridge.TranslationWrapper
import me.rhunk.snapenhance.bridge.common.BridgeMessageType
import me.rhunk.snapenhance.bridge.common.impl.*
import me.rhunk.snapenhance.bridge.common.impl.download.DownloadContentRequest
@@ -111,12 +112,15 @@ class BridgeService : Service() {
}
private fun handleLocaleRequest(reply: (Message) -> Unit) {
- val deviceLocale = Locale.getDefault().toString()
- val compatibleLocale = resources.assets.list("lang")?.find { it.startsWith(deviceLocale) }?.substring(0, 5) ?: "en_US"
+ val locales = sortedSetOf()
+ val contentArray = sortedSetOf()
- resources.assets.open("lang/$compatibleLocale.json").use { inputStream ->
- reply(LocaleResult(compatibleLocale, inputStream.readBytes()).toMessage(BridgeMessageType.LOCALE_RESULT.value))
+ TranslationWrapper.fetchLocales(context = this).forEach { pair ->
+ locales.add(pair.locale)
+ contentArray.add(pair.content)
}
+
+ reply(LocaleResult(locales.toTypedArray(), contentArray.toTypedArray()).toMessage(BridgeMessageType.LOCALE_RESULT.value))
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/MainActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/MainActivity.kt
deleted file mode 100644
index b5a0c2d0..00000000
--- a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/MainActivity.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package me.rhunk.snapenhance.bridge.service
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import me.rhunk.snapenhance.Constants
-
-class MainActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- packageManager.getLaunchIntentForPackage(Constants.SNAPCHAT_PACKAGE_NAME)?.apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(this)
- }
- finish()
- }
-}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt
index b19cc3be..4f8dc1b0 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt
@@ -10,8 +10,7 @@ import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
import java.io.File
enum class ConfigProperty(
- val nameKey: String,
- val descriptionKey: String,
+ val translationKey: String,
val category: ConfigCategory,
val valueContainer: ConfigValue<*>,
val shouldAppearInSettings: Boolean = true,
@@ -19,26 +18,22 @@ enum class ConfigProperty(
) {
//SPYING AND PRIVACY
- MESSAGE_LOGGER("property.message_logger",
- "description.message_logger",
+ MESSAGE_LOGGER("message_logger",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_READ_RECEIPTS(
- "property.prevent_read_receipts",
- "description.prevent_read_receipts",
+ "prevent_read_receipts",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
HIDE_BITMOJI_PRESENCE(
- "property.hide_bitmoji_presence",
- "description.hide_bitmoji_presence",
+ "hide_bitmoji_presence",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
BETTER_NOTIFICATIONS(
- "property.better_notifications",
- "description.better_notifications",
+ "better_notifications",
ConfigCategory.SPYING_PRIVACY,
ConfigStateListValue(
listOf("snap", "chat", "reply_button"),
@@ -50,8 +45,7 @@ enum class ConfigProperty(
)
),
NOTIFICATION_BLACKLIST(
- "property.notification_blacklist",
- "description.notification_blacklist",
+ "notification_blacklist",
ConfigCategory.SPYING_PRIVACY,
ConfigStateListValue(
listOf("snap", "chat", "typing"),
@@ -62,56 +56,51 @@ enum class ConfigProperty(
)
)
),
- DISABLE_METRICS("property.disable_metrics",
- "description.disable_metrics",
+ DISABLE_METRICS("disable_metrics",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
- BLOCK_ADS("property.block_ads",
- "description.block_ads",
+ BLOCK_ADS("block_ads",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
- UNLIMITED_SNAP_VIEW_TIME("property.unlimited_snap_view_time",
- "description.unlimited_snap_view_time",
+ UNLIMITED_SNAP_VIEW_TIME("unlimited_snap_view_time",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_SCREENSHOT_NOTIFICATIONS(
- "property.prevent_screenshot_notifications",
- "description.prevent_screenshot_notifications",
+ "prevent_screenshot_notifications",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_STATUS_NOTIFICATIONS(
- "property.prevent_status_notifications",
- "description.prevent_status_notifications",
+ "prevent_status_notifications",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
ANONYMOUS_STORY_VIEW(
- "property.anonymous_story_view",
- "description.anonymous_story_view",
+ "anonymous_story_view",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
HIDE_TYPING_NOTIFICATION(
- "property.hide_typing_notification",
- "description.hide_typing_notification",
+ "hide_typing_notification",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
//MEDIA MANAGEMENT
SAVE_FOLDER(
- "property.save_folder", "description.save_folder", ConfigCategory.MEDIA_MANAGEMENT,
+ "save_folder",
+ ConfigCategory.MEDIA_MANAGEMENT,
ConfigStringValue(File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath + "/Snapchat",
"SnapEnhance"
).absolutePath)
),
AUTO_DOWNLOAD_OPTIONS(
- "property.auto_download_options", "description.auto_download_options", ConfigCategory.MEDIA_MANAGEMENT,
+ "auto_download_options",
+ ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateListValue(
listOf("friend_snaps", "friend_stories", "public_stories", "spotlight"),
mutableMapOf(
@@ -123,7 +112,8 @@ enum class ConfigProperty(
)
),
DOWNLOAD_OPTIONS(
- "property.download_options", "description.download_options", ConfigCategory.MEDIA_MANAGEMENT,
+ "download_options",
+ ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateListValue(
listOf("format_user_folder", "format_hash", "format_date_time", "format_username", "merge_overlay"),
mutableMapOf(
@@ -136,22 +126,19 @@ enum class ConfigProperty(
)
),
CHAT_DOWNLOAD_CONTEXT_MENU(
- "property.chat_download_context_menu",
- "description.chat_download_context_menu",
+ "chat_download_context_menu",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false)
),
GALLERY_MEDIA_SEND_OVERRIDE(
- "property.gallery_media_send_override",
- "description.gallery_media_send_override",
+ "gallery_media_send_override",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateSelection(
listOf("OFF", "NOTE", "SNAP", "LIVE_SNAP"),
"OFF"
)
),
- AUTO_SAVE_MESSAGES("property.auto_save_messages",
- "description.auto_save_messages",
+ AUTO_SAVE_MESSAGES("auto_save_messages",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateListValue(
listOf("CHAT", "SNAP", "NOTE", "EXTERNAL_MEDIA", "STICKER")
@@ -159,22 +146,19 @@ enum class ConfigProperty(
),
FORCE_MEDIA_SOURCE_QUALITY(
- "property.force_media_source_quality",
- "description.force_media_source_quality",
+ "force_media_source_quality",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false)
),
//UI AND TWEAKS
ENABLE_FRIEND_FEED_MENU_BAR(
- "property.enable_friend_feed_menu_bar",
- "description.enable_friend_feed_menu_bar",
+ "enable_friend_feed_menu_bar",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
FRIEND_FEED_MENU_BUTTONS(
- "property.friend_feed_menu_buttons",
- "description.friend_feed_menu_buttons",
+ "friend_feed_menu_buttons",
ConfigCategory.UI_TWEAKS,
ConfigStateListValue(
listOf("auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info"),
@@ -186,14 +170,12 @@ enum class ConfigProperty(
)
)
),
- FRIEND_FEED_MENU_POSITION("property.friend_feed_menu_buttons_position",
- "description.friend_feed_menu_buttons_position",
+ FRIEND_FEED_MENU_POSITION("friend_feed_menu_buttons_position",
ConfigCategory.UI_TWEAKS,
ConfigIntegerValue(1)
),
HIDE_UI_ELEMENTS(
- "property.hide_ui_elements",
- "description.hide_ui_elements",
+ "hide_ui_elements",
ConfigCategory.UI_TWEAKS,
ConfigStateListValue(
listOf("remove_voice_record_button", "remove_stickers_button", "remove_cognac_button", "remove_call_buttons", "remove_camera_borders"),
@@ -207,74 +189,62 @@ enum class ConfigProperty(
)
),
STREAK_EXPIRATION_INFO(
- "property.streak_expiration_info",
- "description.streakexpirationinfo",
+ "streak_expiration_info",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
DISABLE_SNAP_SPLITTING(
- "property.disable_snap_splitting",
- "description.disable_snap_splitting",
+ "disable_snap_splitting",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
DISABLE_VIDEO_LENGTH_RESTRICTION(
- "property.disable_video_length_restriction",
- "description.disable_video_length_restriction",
+ "disable_video_length_restriction",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
- SNAPCHAT_PLUS("property.snapchat_plus",
- "description.snapchat_plus",
+ SNAPCHAT_PLUS("snapchat_plus",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
- NEW_MAP_UI("property.new_map_ui",
- "description.new_map_ui",
+ NEW_MAP_UI("new_map_ui",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
LOCATION_SPOOF(
- "property.location_spoof",
- "description.location_spoof",
+ "location_spoof",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
LATITUDE(
- "property.latitude_value",
- "description.latitude_value",
+ "latitude_value",
ConfigCategory.UI_TWEAKS,
ConfigStringValue("0.0000"),
shouldAppearInSettings = false
),
LONGITUDE(
- "property.longitude_value",
- "description.longitude_value",
+ "longitude_value",
ConfigCategory.UI_TWEAKS,
ConfigStringValue("0.0000"),
shouldAppearInSettings = false
),
MESSAGE_PREVIEW_LENGTH(
- "property.message_preview_length",
- "description.message_preview_length",
+ "message_preview_length",
ConfigCategory.UI_TWEAKS,
ConfigIntegerValue(20)
),
UNLIMITED_CONVERSATION_PINNING(
- "property.unlimited_conversation_pinning",
- "description.unlimited_conversation_pinning",
+ "unlimited_conversation_pinning",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
DISABLE_SPOTLIGHT(
- "property.disable_spotlight",
- "description.disable_spotlight",
+ "disable_spotlight",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
ENABLE_APP_APPEARANCE(
- "property.enable_app_appearance",
- "description.enable_app_appearance",
+ "enable_app_appearance",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
@@ -282,20 +252,17 @@ enum class ConfigProperty(
//CAMERA
CAMERA_DISABLE(
- "property.disable_camera",
- "description.disable_camera",
+ "disable_camera",
ConfigCategory.CAMERA,
ConfigStateValue(false)
),
IMMERSIVE_CAMERA_PREVIEW(
- "property.immersive_camera_preview",
- "description.immersive_camera_preview",
+ "immersive_camera_preview",
ConfigCategory.CAMERA,
ConfigStateValue(false)
),
OVERRIDE_PREVIEW_RESOLUTION(
- "property.preview_resolution",
- "description.preview_resolution",
+ "preview_resolution",
ConfigCategory.CAMERA,
ConfigStateSelection(
CameraTweaks.resolutions,
@@ -304,8 +271,7 @@ enum class ConfigProperty(
disableValueLocalization = true
),
OVERRIDE_PICTURE_RESOLUTION(
- "property.picture_resolution",
- "description.picture_resolution",
+ "picture_resolution",
ConfigCategory.CAMERA,
ConfigStateSelection(
CameraTweaks.resolutions,
@@ -314,22 +280,19 @@ enum class ConfigProperty(
disableValueLocalization = true
),
FORCE_HIGHEST_FRAME_RATE(
- "property.force_highest_frame_rate",
- "description.force_highest_frame_rate",
+ "force_highest_frame_rate",
ConfigCategory.CAMERA,
ConfigStateValue(false)
),
FORCE_CAMERA_SOURCE_ENCODING(
- "property.force_camera_source_encoding",
- "description.force_camera_source_encoding",
+ "force_camera_source_encoding",
ConfigCategory.CAMERA,
ConfigStateValue(false)
),
// UPDATES
AUTO_UPDATER(
- "property.auto_updater",
- "description.auto_updater",
+ "auto_updater",
ConfigCategory.UPDATES,
ConfigStateSelection(
listOf("DISABLED", "EVERY_LAUNCH", "DAILY", "WEEKLY"),
@@ -339,32 +302,27 @@ enum class ConfigProperty(
// EXPERIMENTAL DEBUGGING
APP_PASSCODE(
- "property.app_passcode",
- "description.app_passcode",
+ "app_passcode",
ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStringValue("", isHidden = true)
),
APP_LOCK_ON_RESUME(
- "property.app_lock_on_resume",
- "description.app_lock_on_resume",
+ "app_lock_on_resume",
ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false)
),
INFINITE_STORY_BOOST(
- "property.infinite_story_boost",
- "description.infinite_story_boost",
+ "infinite_story_boost",
ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false)
),
MEO_PASSCODE_BYPASS(
- "property.meo_passcode_bypass",
- "description.meo_passcode_bypass",
+ "meo_passcode_bypass",
ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false)
),
AMOLED_DARK_MODE(
- "property.amoled_dark_mode",
- "description.amoled_dark_mode",
+ "amoled_dark_mode",
ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false)
);
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/data/LocalePair.kt b/app/src/main/kotlin/me/rhunk/snapenhance/data/LocalePair.kt
new file mode 100644
index 00000000..05a8b8b1
--- /dev/null
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/data/LocalePair.kt
@@ -0,0 +1,3 @@
+package me.rhunk.snapenhance.data
+
+data class LocalePair(val locale: String, val content: String)
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/download/ClientDownloadManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerClient.kt
similarity index 94%
rename from app/src/main/kotlin/me/rhunk/snapenhance/download/ClientDownloadManager.kt
rename to app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerClient.kt
index 89e6f03c..9cb6ea2a 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/download/ClientDownloadManager.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerClient.kt
@@ -8,7 +8,7 @@ import me.rhunk.snapenhance.download.data.DownloadRequest
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
import me.rhunk.snapenhance.download.enums.DownloadMediaType
-class ClientDownloadManager (
+class DownloadManagerClient (
private val context: ModContext,
private val outputPath: String,
private val mediaDisplaySource: String?,
@@ -17,8 +17,8 @@ class ClientDownloadManager (
) {
private fun sendToBroadcastReceiver(bundle: Bundle) {
val intent = Intent()
- intent.setClassName(BuildConfig.APPLICATION_ID, MediaDownloadReceiver::class.java.name)
- intent.action = MediaDownloadReceiver.DOWNLOAD_ACTION
+ intent.setClassName(BuildConfig.APPLICATION_ID, DownloadManagerReceiver::class.java.name)
+ intent.action = DownloadManagerReceiver.DOWNLOAD_ACTION
intent.putExtras(bundle)
context.androidContext.sendBroadcast(intent)
}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/download/MediaDownloadReceiver.kt b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerReceiver.kt
similarity index 90%
rename from app/src/main/kotlin/me/rhunk/snapenhance/download/MediaDownloadReceiver.kt
rename to app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerReceiver.kt
index 1b649432..d13d834d 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/download/MediaDownloadReceiver.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadManagerReceiver.kt
@@ -23,6 +23,7 @@ import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
import me.rhunk.snapenhance.download.data.PendingDownload
import me.rhunk.snapenhance.download.enums.DownloadMediaType
import me.rhunk.snapenhance.download.enums.DownloadStage
+import me.rhunk.snapenhance.SharedContext
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
import java.io.File
@@ -48,13 +49,16 @@ data class DownloadedFile(
)
/**
- * MediaDownloadReceiver handles the download of media files
+ * DownloadManagerReceiver handles the download requests of the user
*/
@OptIn(ExperimentalEncodingApi::class)
-class MediaDownloadReceiver : BroadcastReceiver() {
+class DownloadManagerReceiver : BroadcastReceiver() {
companion object {
- val downloadTaskManager = DownloadTaskManager()
- const val DOWNLOAD_ACTION = "me.rhunk.snapenhance.download.MediaDownloadReceiver.DOWNLOAD_ACTION"
+ const val DOWNLOAD_ACTION = "me.rhunk.snapenhance.download.DownloadManagerReceiver.DOWNLOAD_ACTION"
+ }
+
+ private val translation by lazy {
+ SharedContext.translation.getCategory("download_manager_receiver")
}
private lateinit var context: Context
@@ -124,13 +128,15 @@ class MediaDownloadReceiver : BroadcastReceiver() {
if (!it.endsWith("/")) "$it/" else it
}
- longToast("Saved media to ${outputFile.absolutePath.replace(parentName ?: "", "")}")
+ longToast(
+ translation.format("saved_toast", "path" to outputFile.absolutePath.replace(parentName ?: "", ""))
+ )
pendingDownload.outputFile = outputFile.absolutePath
pendingDownload.downloadStage = DownloadStage.SAVED
}.onFailure {
- Logger.error("Failed to save media to gallery", it)
- longToast("Failed to save media to gallery")
+ Logger.error(it)
+ longToast(translation.format("failed_gallery_toast", "error" to it.toString()))
pendingDownload.downloadStage = DownloadStage.FAILED
}
}
@@ -215,7 +221,7 @@ class MediaDownloadReceiver : BroadcastReceiver() {
val xmlData = dashPlaylistFile.outputStream()
TransformerFactory.newInstance().newTransformer().transform(DOMSource(playlistXml), StreamResult(xmlData))
- longToast("Downloading dash media...")
+ longToast(translation.format("download_toast", "path" to dashPlaylistFile.nameWithoutExtension))
val outputFile = File.createTempFile("dash", ".mp4")
runCatching {
MediaDownloaderHelper.downloadDashChapterFile(
@@ -226,8 +232,8 @@ class MediaDownloadReceiver : BroadcastReceiver() {
saveMediaToGallery(outputFile, pendingDownloadObject)
}.onFailure {
if (coroutineContext.job.isCancelled) return@onFailure
- Logger.error("failed to download dash media", it)
- longToast("Failed to download dash media: ${it.message}")
+ Logger.error(it)
+ longToast(translation.format("failed_processing_toast", "error" to it.toString()))
pendingDownloadObject.downloadStage = DownloadStage.FAILED
}
@@ -247,14 +253,15 @@ class MediaDownloadReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != DOWNLOAD_ACTION) return
this.context = context
- downloadTaskManager.init(context)
+ SharedContext.ensureInitialized(context)
+
val downloadRequest = DownloadRequest.fromBundle(intent.extras!!)
GlobalScope.launch(Dispatchers.IO) {
val pendingDownloadObject = PendingDownload.fromBundle(intent.extras!!)
- downloadTaskManager.addTask(pendingDownloadObject)
+ SharedContext.downloadTaskManager.addTask(pendingDownloadObject)
pendingDownloadObject.apply {
job = coroutineContext.job
downloadStage = DownloadStage.DOWNLOADING
@@ -295,7 +302,7 @@ class MediaDownloadReceiver : BroadcastReceiver() {
val renamedOverlayMedia = renameFromFileType(overlayMedia.file, overlayMedia.fileType)
val mergedOverlay: File = File.createTempFile("merged", "." + media.fileType.fileExtension)
runCatching {
- longToast("Merging overlay...")
+ longToast(translation.format("download_toast", "path" to media.file.nameWithoutExtension))
pendingDownloadObject.downloadStage = DownloadStage.MERGING
MediaDownloaderHelper.mergeOverlayFile(
@@ -307,8 +314,8 @@ class MediaDownloadReceiver : BroadcastReceiver() {
saveMediaToGallery(mergedOverlay, pendingDownloadObject)
}.onFailure {
if (coroutineContext.job.isCancelled) return@onFailure
- Logger.error("failed to merge overlay", it)
- longToast("Failed to merge overlay: ${it.message}")
+ Logger.error(it)
+ longToast(translation.format("failed_processing_toast", "error" to it.toString()))
pendingDownloadObject.downloadStage = DownloadStage.MERGE_FAILED
}
@@ -321,8 +328,8 @@ class MediaDownloadReceiver : BroadcastReceiver() {
downloadRemoteMedia(pendingDownloadObject, downloadedMedias, downloadRequest)
}.onFailure {
pendingDownloadObject.downloadStage = DownloadStage.FAILED
- Logger.error("failed to download media", it)
- longToast("Failed to download media: ${it.message}")
+ Logger.error(it)
+ longToast(translation["failed_generic_toast"])
}
}
}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/download/data/PendingDownload.kt b/app/src/main/kotlin/me/rhunk/snapenhance/download/data/PendingDownload.kt
index fb1b1d85..50e43911 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/download/data/PendingDownload.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/download/data/PendingDownload.kt
@@ -2,7 +2,7 @@ package me.rhunk.snapenhance.download.data
import android.os.Bundle
import kotlinx.coroutines.Job
-import me.rhunk.snapenhance.download.MediaDownloadReceiver
+import me.rhunk.snapenhance.SharedContext
import me.rhunk.snapenhance.download.enums.DownloadStage
data class PendingDownload(
@@ -35,7 +35,7 @@ data class PendingDownload(
set(value) = synchronized(this) {
changeListener(_stage, value)
_stage = value
- MediaDownloadReceiver.downloadTaskManager.updateTask(this)
+ SharedContext.downloadTaskManager.updateTask(this)
}
fun isJobActive(): Boolean {
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt
index e9c5df19..59a8c529 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt
@@ -62,21 +62,21 @@ class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVI
context.runOnUiThread {
AlertDialog.Builder(context.mainActivity)
- .setTitle(context.translation.get("auto_updater.dialog_title"))
+ .setTitle(context.translation["auto_updater.dialog_title"])
.setMessage(
- context.translation.get("auto_updater.dialog_message")
- .replace("{version}", latestVersion)
- .replace("{body}", releaseContentBody)
+ context.translation.format("auto_updater.dialog_message",
+ "version" to latestVersion,
+ "body" to releaseContentBody)
)
- .setNegativeButton(context.translation.get("auto_updater.dialog_negative_button")) { dialog, _ ->
+ .setNegativeButton(context.translation["auto_updater.dialog_negative_button"]) { dialog, _ ->
dialog.dismiss()
}
- .setPositiveButton(context.translation.get("auto_updater.dialog_positive_button")) { dialog, _ ->
+ .setPositiveButton(context.translation["auto_updater.dialog_positive_button"]) { dialog, _ ->
dialog.dismiss()
- context.longToast(context.translation.get("auto_updater.downloading_toast"))
+ context.longToast(context.translation["auto_updater.downloading_toast"])
val request = DownloadManager.Request(Uri.parse(downloadEndpoint))
- .setTitle(context.translation.get("auto_updater.download_manager_notification_title"))
+ .setTitle(context.translation["auto_updater.download_manager_notification_title"])
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "latest-snapenhance.apk")
.setMimeType("application/vnd.android.package-archive")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt
index 8c1cb278..34c5ad5a 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt
@@ -19,7 +19,7 @@ import me.rhunk.snapenhance.data.wrapper.impl.media.dash.SnapPlaylistItem
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.ParamMap
import me.rhunk.snapenhance.database.objects.FriendInfo
-import me.rhunk.snapenhance.download.ClientDownloadManager
+import me.rhunk.snapenhance.download.DownloadManagerClient
import me.rhunk.snapenhance.download.data.toKeyPair
import me.rhunk.snapenhance.download.enums.DownloadMediaType
import me.rhunk.snapenhance.features.Feature
@@ -59,7 +59,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
mediaDisplaySource: String? = null,
mediaDisplayType: String? = null,
friendInfo: FriendInfo? = null
- ): ClientDownloadManager {
+ ): DownloadManagerClient {
val iconUrl = friendInfo?.takeIf {
it.bitmojiAvatarId != null && it.bitmojiSelfieId != null
}?.let {
@@ -71,7 +71,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
createNewFilePath(pathSuffix.hashCode(), pathSuffix)
).absolutePath
- return ClientDownloadManager(
+ return DownloadManagerClient(
context = context,
mediaDisplaySource = mediaDisplaySource,
mediaDisplayType = mediaDisplayType,
@@ -143,7 +143,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
}
}
- private fun downloadOperaMedia(clientDownloadManager: ClientDownloadManager, mediaInfoMap: Map) {
+ private fun downloadOperaMedia(downloadManagerClient: DownloadManagerClient, mediaInfoMap: Map) {
if (mediaInfoMap.isEmpty()) return
val originalMediaInfo = mediaInfoMap[MediaType.ORIGINAL]!!
@@ -153,7 +153,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val overlayReference = overlay?.let { handleLocalReferences(it.uri) }
overlay?.let {
- clientDownloadManager.downloadMediaWithOverlay(
+ downloadManagerClient.downloadMediaWithOverlay(
originalMediaInfoReference,
overlayReference!!,
DownloadMediaType.fromUri(Uri.parse(originalMediaInfoReference)),
@@ -164,7 +164,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
return
}
- clientDownloadManager.downloadMedia(
+ downloadManagerClient.downloadMedia(
originalMediaInfoReference,
DownloadMediaType.fromUri(Uri.parse(originalMediaInfoReference)),
originalMediaInfo.encryption?.toKeyPair()
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt
index 40818654..604a04a7 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt
@@ -90,8 +90,7 @@ class FeatureManager(private val context: ModContext) : Manager {
}
private fun featureInitializer(isAsync: Boolean, param: Int, action: (Feature) -> Unit) {
- features.forEach { feature ->
- if (feature.loadParams and param == 0) return@forEach
+ features.filter { it.loadParams and param != 0 }.forEach { feature ->
val callback = {
runCatching {
action(feature)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt
deleted file mode 100644
index 8984d938..00000000
--- a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package me.rhunk.snapenhance.manager.impl
-
-import com.google.gson.JsonObject
-import com.google.gson.JsonParser
-import me.rhunk.snapenhance.Logger
-import me.rhunk.snapenhance.ModContext
-import me.rhunk.snapenhance.manager.Manager
-import java.util.Locale
-
-class TranslationManager(
- private val context: ModContext
-) : Manager {
- private val translationMap = mutableMapOf()
- lateinit var locale: Locale
-
- override fun init() {
- val messageLocaleResult = context.bridgeClient.fetchTranslations()
- locale = messageLocaleResult.locale?.split("_")?.let { Locale(it[0], it[1]) } ?: Locale.getDefault()
-
- val translations = JsonParser.parseString(messageLocaleResult.content?.toString(Charsets.UTF_8)).asJsonObject
- if (translations == null || translations.isJsonNull) {
- context.crash("Failed to fetch translations")
- return
- }
-
- fun scanObject(jsonObject: JsonObject, prefix: String = "") {
- jsonObject.entrySet().forEach {
- if (it.value.isJsonPrimitive) {
- translationMap["$prefix${it.key}"] = it.value.asString
- }
- if (!it.value.isJsonObject) return@forEach
- scanObject(it.value.asJsonObject, "$prefix${it.key}.")
- }
- }
-
- scanObject(translations)
- }
-
-
- fun get(key: String): String {
- return translationMap[key] ?: key.also { Logger.xposedLog("Missing translation for $key") }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt
index 233d7744..ce567497 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt
@@ -31,6 +31,7 @@ import java.net.URL
import kotlin.concurrent.thread
class DownloadListAdapter(
+ private val activity: DownloadManagerActivity,
private val downloadList: MutableList
): Adapter() {
private val previewJobs = mutableMapOf()
@@ -56,15 +57,6 @@ class DownloadListAdapter(
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.download_manager_item, parent, false))
}
- override fun onViewRecycled(holder: ViewHolder) {
- val download = downloadList.getOrNull(holder.bindingAdapterPosition) ?: return
-
- previewJobs[holder.hashCode()]?.let {
- it.cancel()
- previewJobs.remove(holder.hashCode())
- }
- }
-
override fun getItemCount(): Int {
return downloadList.size
}
@@ -99,6 +91,11 @@ class DownloadListAdapter(
}
}
+ private val openButtonText by lazy {
+ activity.translation["button.open"]
+ }
+
+
private fun updateViewHolder(download: PendingDownload, holder: ViewHolder) {
holder.status.text = download.downloadStage.toString()
holder.view.background = holder.view.context.getDrawable(R.drawable.download_manager_item_background)
@@ -117,7 +114,10 @@ class DownloadListAdapter(
alpha = if (canInteract) 1f else 0.5f
background = context.getDrawable(if (isSaved) R.drawable.action_button_success else R.drawable.action_button_cancel)
setTextColor(context.getColor(if (isSaved) R.color.successColor else R.color.actionBarColor))
- text = if (isSaved) "Open" else "Cancel"
+ text = if (isSaved)
+ activity.translation["button.open"]
+ else
+ activity.translation["button.cancel"]
}
}
@@ -171,7 +171,7 @@ class DownloadListAdapter(
pendingDownload.outputFile?.let {
val file = File(it)
if (!file.exists()) {
- Toast.makeText(holder.view.context, "File does not exist", Toast.LENGTH_SHORT).show()
+ Toast.makeText(holder.view.context, activity.translation["file_not_found_toast"], Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val intent = Intent(Intent.ACTION_VIEW)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt
index 07776f1b..d6a08844 100644
--- a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt
+++ b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt
@@ -10,7 +10,6 @@ import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.view.View
-import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
@@ -18,24 +17,24 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import me.rhunk.snapenhance.BuildConfig
import me.rhunk.snapenhance.R
-import me.rhunk.snapenhance.download.MediaDownloadReceiver
+import me.rhunk.snapenhance.bridge.TranslationWrapper
+import me.rhunk.snapenhance.SharedContext
import me.rhunk.snapenhance.download.data.PendingDownload
class DownloadManagerActivity : Activity() {
+ lateinit var translation: TranslationWrapper
+
private val backCallbacks = mutableListOf<() -> Unit>()
private val fetchedDownloadTasks = mutableListOf()
private var listFilter = MediaFilter.NONE
- private val downloadTaskManager by lazy {
- MediaDownloadReceiver.downloadTaskManager.also { it.init(this) }
- }
-
private val preferences by lazy {
getSharedPreferences("settings", Context.MODE_PRIVATE)
}
private fun updateNoDownloadText() {
- findViewById(R.id.no_download_title).let {
+ findViewById(R.id.no_download_title).let {
+ it.text = translation["no_downloads"]
it.visibility = if (fetchedDownloadTasks.isEmpty()) View.VISIBLE else View.GONE
}
}
@@ -43,7 +42,7 @@ class DownloadManagerActivity : Activity() {
@SuppressLint("NotifyDataSetChanged")
private fun updateListContent() {
fetchedDownloadTasks.clear()
- fetchedDownloadTasks.addAll(downloadTaskManager.queryAllTasks(filter = listFilter).values)
+ fetchedDownloadTasks.addAll(SharedContext.downloadTaskManager.queryAllTasks(filter = listFilter).values)
with(findViewById(R.id.download_list)) {
adapter?.notifyDataSetChanged()
@@ -68,6 +67,8 @@ class DownloadManagerActivity : Activity() {
@SuppressLint("BatteryLife", "NotifyDataSetChanged", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ SharedContext.ensureInitialized(this)
+ translation = SharedContext.translation.getCategory("download_manager_activity")
setContentView(R.layout.download_manager_activity)
@@ -75,13 +76,13 @@ class DownloadManagerActivity : Activity() {
findViewById(R.id.title).text = resources.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME
findViewById(R.id.settings_button).setOnClickListener {
- SettingLayoutInflater(this).inflate(findViewById(android.R.id.content))
+ SettingLayoutInflater(this).inflate(findViewById(android.R.id.content))
}
with(findViewById(R.id.download_list)) {
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this@DownloadManagerActivity)
- adapter = DownloadListAdapter(fetchedDownloadTasks).apply {
+ adapter = DownloadListAdapter(this@DownloadManagerActivity, fetchedDownloadTasks).apply {
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
updateNoDownloadText()
@@ -113,7 +114,7 @@ class DownloadManagerActivity : Activity() {
@SuppressLint("NotifyDataSetChanged")
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
fetchedDownloadTasks.removeAt(viewHolder.absoluteAdapterPosition).let {
- downloadTaskManager.removeTask(it)
+ SharedContext.downloadTaskManager.removeTask(it)
}
adapter?.notifyItemRemoved(viewHolder.absoluteAdapterPosition)
}
@@ -133,7 +134,7 @@ class DownloadManagerActivity : Activity() {
if (lastVisibleItemPosition == fetchedDownloadTasks.size - 1 && !isLoading) {
isLoading = true
- downloadTaskManager.queryTasks(fetchedDownloadTasks.last().id, filter = listFilter).forEach {
+ SharedContext.downloadTaskManager.queryTasks(fetchedDownloadTasks.last().id, filter = listFilter).forEach {
fetchedDownloadTasks.add(it.value)
adapter?.notifyItemInserted(fetchedDownloadTasks.size - 1)
}
@@ -151,7 +152,9 @@ class DownloadManagerActivity : Activity() {
Pair(R.id.spotlight_category, MediaFilter.SPOTLIGHT)
).let { categoryPairs ->
categoryPairs.forEach { pair ->
- this@DownloadManagerActivity.findViewById(pair.first).setOnClickListener { view ->
+ this@DownloadManagerActivity.findViewById(pair.first).apply {
+ text = translation["category.${resources.getResourceEntryName(pair.first)}"]
+ }.setOnClickListener { view ->
listFilter = pair.second
updateListContent()
categoryPairs.map { this@DownloadManagerActivity.findViewById(it.first) }.forEach {
@@ -162,12 +165,14 @@ class DownloadManagerActivity : Activity() {
}
}
- this@DownloadManagerActivity.findViewById