refactor: translations (#78)
* ui changes * fix more stuff * fix: set ffmpeg preset to ultrafast * refactor: ui tweaks getIdentifier function * feat: download manager filter * add placeholder bitmoji * change bitmoji blank color * add settings page (not done) * setting page impl * confirmation dialog * update theme * remove snap context buttons * fix: delete cache * fix: bitmoji icon * fix: french translation * translation refactor * translation: download manager * refactor: get operator * refactor: config properties * add translation for download manager receiver * fix: steak expire translation format --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
@@ -34,10 +34,10 @@
|
||||
tools:ignore="ExportedService">
|
||||
</service>
|
||||
|
||||
<receiver android:name=".download.MediaDownloadReceiver" android:exported="true"
|
||||
<receiver android:name=".download.DownloadManagerReceiver" android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="me.rhunk.snapenhance.download.MediaDownloadReceiver.DOWNLOAD_ACTION" />
|
||||
<action android:name="me.rhunk.snapenhance.download.DownloadManagerReceiver.DOWNLOAD_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
@@ -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 !",
|
||||
|
||||
@@ -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)
|
||||
|
||||
26
app/src/main/kotlin/me/rhunk/snapenhance/SharedContext.kt
Normal file
26
app/src/main/kotlin/me/rhunk/snapenhance/SharedContext.kt
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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<FriendFeedInfo>()
|
||||
|
||||
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<Job>()
|
||||
|
||||
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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<LocalePair> {
|
||||
val deviceLocale = Locale.getDefault().toString()
|
||||
val locales = mutableListOf<LocalePair>()
|
||||
|
||||
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<String, String>()
|
||||
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, String>): 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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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<String>? = null,
|
||||
private var localContentArray: Array<String>? = null
|
||||
) : BridgeMessage(){
|
||||
override fun write(bundle: Bundle) {
|
||||
bundle.putString("locale", locale)
|
||||
bundle.putByteArray("content", content)
|
||||
|
||||
fun getLocales(): List<LocalePair> {
|
||||
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<String>
|
||||
}
|
||||
}
|
||||
@@ -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<String>()
|
||||
val contentArray = sortedSetOf<String>()
|
||||
|
||||
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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package me.rhunk.snapenhance.data
|
||||
|
||||
data class LocalePair(val locale: String, val content: String)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<MediaType, MediaInfo>) {
|
||||
private fun downloadOperaMedia(downloadManagerClient: DownloadManagerClient, mediaInfoMap: Map<MediaType, MediaInfo>) {
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String, String>()
|
||||
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") }
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import java.net.URL
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class DownloadListAdapter(
|
||||
private val activity: DownloadManagerActivity,
|
||||
private val downloadList: MutableList<PendingDownload>
|
||||
): Adapter<DownloadListAdapter.ViewHolder>() {
|
||||
private val previewJobs = mutableMapOf<Int, Job>()
|
||||
@@ -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)
|
||||
|
||||
@@ -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<PendingDownload>()
|
||||
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<View>(R.id.no_download_title).let {
|
||||
findViewById<TextView>(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<RecyclerView>(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<TextView>(R.id.title).text = resources.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME
|
||||
|
||||
findViewById<ImageButton>(R.id.settings_button).setOnClickListener {
|
||||
SettingLayoutInflater(this).inflate(findViewById<ViewGroup>(android.R.id.content))
|
||||
SettingLayoutInflater(this).inflate(findViewById(android.R.id.content))
|
||||
}
|
||||
|
||||
with(findViewById<RecyclerView>(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<TextView>(pair.first).setOnClickListener { view ->
|
||||
this@DownloadManagerActivity.findViewById<TextView>(pair.first).apply {
|
||||
text = translation["category.${resources.getResourceEntryName(pair.first)}"]
|
||||
}.setOnClickListener { view ->
|
||||
listFilter = pair.second
|
||||
updateListContent()
|
||||
categoryPairs.map { this@DownloadManagerActivity.findViewById<TextView>(it.first) }.forEach {
|
||||
@@ -162,12 +165,14 @@ class DownloadManagerActivity : Activity() {
|
||||
}
|
||||
}
|
||||
|
||||
this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button).setOnClickListener {
|
||||
this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button).also {
|
||||
it.text = translation["remove_all"]
|
||||
}.setOnClickListener {
|
||||
with(AlertDialog.Builder(this@DownloadManagerActivity)) {
|
||||
setTitle(R.string.remove_all_title)
|
||||
setMessage(R.string.remove_all_text)
|
||||
setPositiveButton("Yes") { _, _ ->
|
||||
downloadTaskManager.removeAllTasks()
|
||||
setTitle(translation["remove_all_title"])
|
||||
setMessage(translation["remove_all_text"])
|
||||
setPositiveButton(translation["button.positive"]) { _, _ ->
|
||||
SharedContext.downloadTaskManager.removeAllTasks()
|
||||
fetchedDownloadTasks.removeIf {
|
||||
if (it.isJobActive()) it.cancel()
|
||||
true
|
||||
@@ -175,7 +180,7 @@ class DownloadManagerActivity : Activity() {
|
||||
adapter?.notifyDataSetChanged()
|
||||
updateNoDownloadText()
|
||||
}
|
||||
setNegativeButton("Cancel") { dialog, _ ->
|
||||
setNegativeButton(translation["button.negative"]) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
|
||||
@@ -39,49 +39,56 @@ class SettingLayoutInflater(
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("Yes") { _, _ ->
|
||||
.setPositiveButton(activity.translation["button.positive"]) { _, _ ->
|
||||
action()
|
||||
}
|
||||
.setNegativeButton("No") { _, _ -> }
|
||||
.setNegativeButton(activity.translation["button.negative"]) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSuccessToast() {
|
||||
Toast.makeText(activity, "Success", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun inflate(parent: ViewGroup) {
|
||||
val settingsView = activity.layoutInflater.inflate(R.layout.settings_page, parent, false)
|
||||
|
||||
val settingTranslation = activity.translation.getCategory("settings_page")
|
||||
|
||||
settingsView.findViewById<ImageButton>(R.id.settings_button).setOnClickListener {
|
||||
parent.removeView(settingsView)
|
||||
}
|
||||
|
||||
settingsView.findViewById<TextView>(R.id.title).text = activity.translation["settings"]
|
||||
|
||||
settingsView.findViewById<ListView>(R.id.setting_page_list).apply {
|
||||
adapter = SettingAdapter(activity, R.layout.setting_item, mutableListOf<Pair<String, () -> Unit>>().apply {
|
||||
add("Clear Cache" to {
|
||||
add(settingTranslation["clear_cache_title"] to {
|
||||
context.cacheDir.listFiles()?.forEach {
|
||||
it.deleteRecursively()
|
||||
}
|
||||
Toast.makeText(context, "Cache cleared", Toast.LENGTH_SHORT).show()
|
||||
showSuccessToast()
|
||||
})
|
||||
|
||||
BridgeFileType.values().forEach { fileType ->
|
||||
val actionName = "Clear ${fileType.displayName} File"
|
||||
val actionName = settingTranslation.format("clear_file_title", "file_name" to fileType.displayName)
|
||||
add(actionName to {
|
||||
confirmAction(actionName, "Are you sure you want to clear ${fileType.displayName} file?") {
|
||||
confirmAction(actionName, settingTranslation.format("clear_file_confirmation", "file_name" to fileType.displayName)) {
|
||||
fileType.resolve(context).deleteRecursively()
|
||||
Toast.makeText(context, "${fileType.displayName} file cleared", Toast.LENGTH_SHORT).show()
|
||||
showSuccessToast()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
add("Reset All" to {
|
||||
confirmAction("Reset All", "Are you sure you want to reset all?") {
|
||||
add(settingTranslation["reset_all_title"] to {
|
||||
confirmAction(settingTranslation["reset_all_title"], settingTranslation["reset_all_confirmation"]) {
|
||||
arrayOf(context.cacheDir, context.filesDir, File(context.dataDir, "databases"), File(context.dataDir, "shared_prefs")).forEach {
|
||||
it.listFiles()?.forEach { file ->
|
||||
file.deleteRecursively()
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, "Success!", Toast.LENGTH_SHORT).show()
|
||||
showSuccessToast()
|
||||
}
|
||||
})
|
||||
}.toTypedArray())
|
||||
|
||||
@@ -57,7 +57,7 @@ class ChatActionMenu : AbstractMenu() {
|
||||
|
||||
if (context.config.bool(ConfigProperty.CHAT_DOWNLOAD_CONTEXT_MENU)) {
|
||||
injectButton(Button(viewGroup.context).apply {
|
||||
text = this@ChatActionMenu.context.translation.get("chat_action_menu.preview_button")
|
||||
text = this@ChatActionMenu.context.translation["chat_action_menu.preview_button"]
|
||||
setOnClickListener {
|
||||
closeActionMenu()
|
||||
this@ChatActionMenu.context.executeAsync { this@ChatActionMenu.context.feature(MediaDownloader::class).onMessageActionMenu(true) }
|
||||
@@ -65,7 +65,7 @@ class ChatActionMenu : AbstractMenu() {
|
||||
})
|
||||
|
||||
injectButton(Button(viewGroup.context).apply {
|
||||
text = this@ChatActionMenu.context.translation.get("chat_action_menu.download_button")
|
||||
text = this@ChatActionMenu.context.translation["chat_action_menu.download_button"]
|
||||
setOnClickListener {
|
||||
closeActionMenu()
|
||||
this@ChatActionMenu.context.executeAsync {
|
||||
@@ -80,7 +80,7 @@ class ChatActionMenu : AbstractMenu() {
|
||||
//delete logged message button
|
||||
if (context.config.bool(ConfigProperty.MESSAGE_LOGGER)) {
|
||||
injectButton(Button(viewGroup.context).apply {
|
||||
text = this@ChatActionMenu.context.translation.get("chat_action_menu.delete_logged_message_button")
|
||||
text = this@ChatActionMenu.context.translation["chat_action_menu.delete_logged_message_button"]
|
||||
setOnClickListener {
|
||||
closeActionMenu()
|
||||
this@ChatActionMenu.context.executeAsync {
|
||||
|
||||
@@ -80,17 +80,18 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
val birthday = Calendar.getInstance()
|
||||
birthday[Calendar.MONTH] = (profile.birthday shr 32).toInt() - 1
|
||||
val message: String = """
|
||||
${context.translation.get("profile_info.username")}: ${profile.username}
|
||||
${context.translation.get("profile_info.display_name")}: ${profile.displayName}
|
||||
${context.translation.get("profile_info.added_date")}: ${formatDate(addedTimestamp)}
|
||||
${context.translation["profile_info.username"]}: ${profile.username}
|
||||
${context.translation["profile_info.display_name"]}: ${profile.displayName}
|
||||
${context.translation["profile_info.added_date"]}: ${formatDate(addedTimestamp)}
|
||||
${birthday.getDisplayName(
|
||||
Calendar.MONTH,
|
||||
Calendar.LONG,
|
||||
context.translation.locale
|
||||
)?.let {
|
||||
context.translation.get("profile_info.birthday")
|
||||
.replace("{month}", it)
|
||||
.replace("{day}", profile.birthday.toInt().toString())
|
||||
context.translation.format("profile_info.birthday",
|
||||
"month" to it,
|
||||
"day" to birthday[Calendar.DAY_OF_MONTH].toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
@@ -136,7 +137,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
var displayUsername = sender?.displayName ?: sender?.usernameForSorting?: context.translation.get("conversation_preview.unknown_user")
|
||||
var displayUsername = sender?.displayName ?: sender?.usernameForSorting?: context.translation["conversation_preview.unknown_user"]
|
||||
|
||||
if (displayUsername.length > 12) {
|
||||
displayUsername = displayUsername.substring(0, 13) + "... "
|
||||
@@ -152,22 +153,23 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
val timeSecondDiff = ((it - System.currentTimeMillis()) / 1000 / 60).toInt()
|
||||
messageBuilder.append("\n\n")
|
||||
.append("\uD83D\uDD25 ") //fire emoji
|
||||
.append(context.translation.get("conversation_preview.streak_expiration").format(
|
||||
timeSecondDiff / 60 / 24,
|
||||
timeSecondDiff / 60 % 24,
|
||||
timeSecondDiff % 60
|
||||
.append(
|
||||
context.translation.format("conversation_preview.streak_expiration",
|
||||
"day" to (timeSecondDiff / 60 / 24).toString(),
|
||||
"hour" to (timeSecondDiff / 60 % 24).toString(),
|
||||
"minute" to (timeSecondDiff % 60).toString()
|
||||
))
|
||||
}
|
||||
|
||||
//alert dialog
|
||||
val builder = AlertDialog.Builder(context.mainActivity)
|
||||
builder.setTitle(context.translation.get("conversation_preview.title"))
|
||||
builder.setTitle(context.translation["conversation_preview.title"])
|
||||
builder.setMessage(messageBuilder.toString())
|
||||
builder.setPositiveButton(
|
||||
"OK"
|
||||
) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
||||
targetPerson?.let {
|
||||
builder.setNegativeButton(context.translation.get("modal_option.profile_info")) {_, _ ->
|
||||
builder.setNegativeButton(context.translation["modal_option.profile_info"]) { _, _ ->
|
||||
context.executeAsync {
|
||||
showProfileInfo(it)
|
||||
}
|
||||
@@ -208,7 +210,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
|
||||
private fun createToggleFeature(viewConsumer: ((View) -> Unit), text: String, isChecked: () -> Boolean, toggle: (Boolean) -> Unit) {
|
||||
val switch = Switch(context.androidContext)
|
||||
switch.text = context.translation.get(text)
|
||||
switch.text = context.translation[text]
|
||||
switch.isChecked = isChecked()
|
||||
ViewAppearanceHelper.applyTheme(switch)
|
||||
switch.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean ->
|
||||
@@ -231,7 +233,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
if (!context.config.bool(ConfigProperty.ENABLE_FRIEND_FEED_MENU_BAR)) {
|
||||
//preview button
|
||||
val previewButton = Button(viewModel.context).apply {
|
||||
text = modContext.translation.get("friend_menu_option.preview")
|
||||
text = modContext.translation["friend_menu_option.preview"]
|
||||
ViewAppearanceHelper.applyTheme(this, viewModel.width)
|
||||
setOnClickListener {
|
||||
showPreview(
|
||||
@@ -244,7 +246,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
|
||||
//stealth switch
|
||||
val stealthSwitch = Switch(viewModel.context).apply {
|
||||
text = modContext.translation.get("friend_menu_option.stealth_mode")
|
||||
text = modContext.translation["friend_menu_option.stealth_mode"]
|
||||
isChecked = modContext.feature(StealthMode::class).isStealth(conversationId)
|
||||
ViewAppearanceHelper.applyTheme(this)
|
||||
setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
|
||||
@@ -70,7 +70,7 @@ class OperaContextActionMenu : AbstractMenu() {
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
val button = Button(childView.getContext())
|
||||
button.text = context.translation.get("opera_context_menu.download")
|
||||
button.text = context.translation["opera_context_menu.download"]
|
||||
button.setOnClickListener { context.feature(MediaDownloader::class).downloadLastOperaMediaAsync() }
|
||||
applyTheme(button)
|
||||
linearLayout.addView(button)
|
||||
|
||||
@@ -26,7 +26,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun createCategoryTitle(key: String): TextView {
|
||||
val categoryText = TextView(context.androidContext)
|
||||
categoryText.text = context.translation.get(key)
|
||||
categoryText.text = context.translation[key]
|
||||
ViewAppearanceHelper.applyTheme(categoryText)
|
||||
categoryText.textSize = 20f
|
||||
categoryText.typeface = categoryText.typeface?.let { Typeface.create(it, Typeface.BOLD) }
|
||||
@@ -36,7 +36,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun createPropertyView(property: ConfigProperty): View {
|
||||
val propertyName = context.translation.get(property.nameKey)
|
||||
val propertyName = context.translation["property.${property.translationKey}"]
|
||||
val updateButtonText: (TextView, String) -> Unit = { textView, text ->
|
||||
textView.text = "$propertyName${if (text.isEmpty()) "" else ": $text"}"
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
if (property.disableValueLocalization) {
|
||||
it
|
||||
} else {
|
||||
context.translation.get("option." + property.nameKey + "." + it)
|
||||
context.translation["option.property." + property.translationKey + "." + it]
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -129,7 +129,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
builder.setSingleChoiceItems(
|
||||
property.valueContainer.keys().toTypedArray().map {
|
||||
if (property.disableValueLocalization) it
|
||||
else context.translation.get("option." + property.nameKey + "." + it)
|
||||
else context.translation["option.property." + property.translationKey + "." + it]
|
||||
}.toTypedArray(),
|
||||
property.valueContainer.keys().indexOf(property.valueContainer.value())
|
||||
) { _, which ->
|
||||
@@ -158,7 +158,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
builder.setMultiChoiceItems(
|
||||
sortedStates.toSortedMap().map {
|
||||
if (property.disableValueLocalization) it.key
|
||||
else context.translation.get("option." + property.nameKey + "." + it.key)
|
||||
else context.translation["option.property." + property.translationKey + "." + it.key]
|
||||
}.toTypedArray(),
|
||||
sortedStates.map { it.value }.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
@@ -212,7 +212,7 @@ class SettingsMenu : AbstractMenu() {
|
||||
val actions = context.actionManager.getActions().map {
|
||||
Pair(it) {
|
||||
val button = Button(viewModel.context)
|
||||
button.text = context.translation.get(it.nameKey)
|
||||
button.text = context.translation[it.nameKey]
|
||||
button.setOnClickListener { _ ->
|
||||
it.run()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
android:src="@drawable/settings_icon"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:padding="8dp"
|
||||
android:contentDescription="@string/settings" />
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/avenir_next_medium"
|
||||
android:gravity="center"
|
||||
android:text="@string/all_category"
|
||||
android:text=""
|
||||
android:textColor="@color/focusedCategoryColor" />
|
||||
|
||||
<TextView
|
||||
@@ -65,7 +65,7 @@
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/avenir_next_medium"
|
||||
android:gravity="center"
|
||||
android:text="@string/pending_category"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText" />
|
||||
|
||||
<TextView
|
||||
@@ -77,7 +77,7 @@
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/avenir_next_medium"
|
||||
android:gravity="center"
|
||||
android:text="@string/snap_category"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText" />
|
||||
|
||||
<TextView
|
||||
@@ -89,7 +89,7 @@
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/avenir_next_medium"
|
||||
android:gravity="center"
|
||||
android:text="@string/story_category"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText" />
|
||||
|
||||
<TextView
|
||||
@@ -101,7 +101,7 @@
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/avenir_next_medium"
|
||||
android:gravity="center"
|
||||
android:text="@string/spotlight_category"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -124,7 +124,7 @@
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="40dp"
|
||||
android:text="@string/no_downloads"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
android:layout_height="45dp"
|
||||
android:background="@drawable/action_button_cancel"
|
||||
android:padding="5dp"
|
||||
android:text="@string/remove_all"
|
||||
android:text=""
|
||||
android:textColor="@color/darkText"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
android:background="@drawable/action_button_cancel"
|
||||
android:padding="5dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@string/cancel"
|
||||
android:text=""
|
||||
android:textColor="@color/darkText"
|
||||
tools:ignore="ButtonOrder" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="@string/settings"
|
||||
android:text=""
|
||||
android:textColor="@color/primaryText"
|
||||
android:textSize="23sp"
|
||||
android:fontFamily="@font/avenir_next_bold" />
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">SnapEnhance</string>
|
||||
<string name="remove_all_title">Remove all Downloads</string>
|
||||
<string name="remove_all_text">Are you sure you want to do this?</string>
|
||||
<string name="remove_all">Remove All</string>
|
||||
<string name="no_downloads">No downloads</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="all_category">All</string>
|
||||
<string name="pending_category">Pending</string>
|
||||
<string name="snap_category">Snaps</string>
|
||||
<string name="story_category">Stories</string>
|
||||
<string name="spotlight_category">Spotlight</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="refresh_mappings">Refresh Mappings</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user