mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 20:40:13 +02:00
feat: log system
- debug actions - move packages to core
This commit is contained in:
parent
6b9e44700d
commit
61da95f41b
189
app/src/main/kotlin/me/rhunk/snapenhance/LogManager.kt
Normal file
189
app/src/main/kotlin/me/rhunk/snapenhance/LogManager.kt
Normal file
@ -0,0 +1,189 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
class LogLine(
|
||||
val logLevel: LogLevel,
|
||||
val dateTime: String,
|
||||
val tag: String,
|
||||
val message: String
|
||||
) {
|
||||
companion object {
|
||||
fun fromString(line: String) = runCatching {
|
||||
val parts = line.trimEnd().split("/")
|
||||
if (parts.size != 4) return@runCatching null
|
||||
LogLine(
|
||||
LogLevel.fromLetter(parts[0]) ?: return@runCatching null,
|
||||
parts[1],
|
||||
parts[2],
|
||||
parts[3]
|
||||
)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${logLevel.letter}/$dateTime/$tag/$message"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogReader(
|
||||
logFile: File
|
||||
) {
|
||||
private val randomAccessFile = RandomAccessFile(logFile, "r")
|
||||
private var startLineIndexes = mutableListOf<Long>()
|
||||
var lineCount = queryLineCount()
|
||||
|
||||
fun incrementLineCount() {
|
||||
randomAccessFile.seek(randomAccessFile.length())
|
||||
startLineIndexes.add(randomAccessFile.filePointer)
|
||||
lineCount++
|
||||
}
|
||||
|
||||
private fun queryLineCount(): Int {
|
||||
randomAccessFile.seek(0)
|
||||
var lines = 0
|
||||
var lastIndex: Long
|
||||
while (true) {
|
||||
lastIndex = randomAccessFile.filePointer
|
||||
randomAccessFile.readLine() ?: break
|
||||
startLineIndexes.add(lastIndex)
|
||||
lines++
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
private fun getLine(index: Int): String? {
|
||||
if (index <= 0 || index > lineCount) return null
|
||||
randomAccessFile.seek(startLineIndexes[index])
|
||||
return randomAccessFile.readLine()
|
||||
}
|
||||
|
||||
fun getLogLine(index: Int): LogLine? {
|
||||
return getLine(index)?.let { LogLine.fromString(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogManager(
|
||||
remoteSideContext: RemoteSideContext
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "SnapEnhanceManager"
|
||||
private val LOG_LIFETIME = 24.hours
|
||||
}
|
||||
|
||||
var lineAddListener = { _: LogLine -> }
|
||||
|
||||
private val logFolder = File(remoteSideContext.androidContext.cacheDir, "logs")
|
||||
private val preferences: SharedPreferences
|
||||
|
||||
private var logFile: File
|
||||
|
||||
init {
|
||||
if (!logFolder.exists()) {
|
||||
logFolder.mkdirs()
|
||||
}
|
||||
preferences = remoteSideContext.androidContext.getSharedPreferences("logger", 0)
|
||||
logFile = preferences.getString("log_file", null)?.let { File(it) }?.takeIf { it.exists() } ?: run {
|
||||
newLogFile()
|
||||
logFile
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - preferences.getLong("last_created", 0) > LOG_LIFETIME.inWholeMilliseconds) {
|
||||
newLogFile()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentDateTime(pathSafe: Boolean = false): String {
|
||||
return DateTimeFormatter.ofPattern(if (pathSafe) "yyyy-MM-dd_HH-mm-ss" else "yyyy-MM-dd HH:mm:ss").format(
|
||||
java.time.LocalDateTime.now()
|
||||
)
|
||||
}
|
||||
|
||||
private fun newLogFile() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
logFile = File(logFolder, "snapenhance_${getCurrentDateTime(pathSafe = true)}.log").also {
|
||||
it.createNewFile()
|
||||
}
|
||||
preferences.edit().putString("log_file", logFile.absolutePath).putLong("last_created", currentTime).apply()
|
||||
}
|
||||
|
||||
fun clearLogs() {
|
||||
logFile.delete()
|
||||
newLogFile()
|
||||
}
|
||||
|
||||
fun getLogFile() = logFile
|
||||
|
||||
fun exportLogsToZip(outputStream: OutputStream) {
|
||||
val zipOutputStream = ZipOutputStream(outputStream)
|
||||
//add logFolder to zip
|
||||
logFolder.walk().forEach {
|
||||
if (it.isFile) {
|
||||
zipOutputStream.putNextEntry(ZipEntry(it.name))
|
||||
it.inputStream().copyTo(zipOutputStream)
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
//add device info to zip
|
||||
zipOutputStream.putNextEntry(ZipEntry("device_info.txt"))
|
||||
|
||||
|
||||
zipOutputStream.close()
|
||||
}
|
||||
|
||||
fun newReader(onAddLine: (LogLine) -> Unit) = LogReader(logFile).also {
|
||||
lineAddListener = { line -> it.incrementLineCount(); onAddLine(line) }
|
||||
}
|
||||
|
||||
fun debug(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.DEBUG, message)
|
||||
}
|
||||
|
||||
fun error(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
}
|
||||
|
||||
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
internalLog(tag, LogLevel.ERROR, throwable)
|
||||
}
|
||||
|
||||
fun info(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.INFO, message)
|
||||
}
|
||||
|
||||
fun verbose(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.VERBOSE, message)
|
||||
}
|
||||
|
||||
fun warn(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.WARN, message)
|
||||
}
|
||||
|
||||
fun assert(message: Any?, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.ASSERT, message)
|
||||
}
|
||||
|
||||
fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
|
||||
runCatching {
|
||||
val line = LogLine(logLevel, getCurrentDateTime(), tag, message.toString())
|
||||
logFile.appendText("$line\n", Charsets.UTF_8)
|
||||
lineAddListener(line)
|
||||
Log.println(logLevel.priority, tag, message.toString())
|
||||
}.onFailure {
|
||||
Log.println(Log.ERROR, tag, "Failed to log message: $message")
|
||||
Log.println(Log.ERROR, tag, it.toString())
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,12 @@ package me.rhunk.snapenhance
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.core.app.CoreComponentFactory
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import coil.ImageLoader
|
||||
import coil.decode.VideoFrameDecoder
|
||||
@ -13,18 +16,25 @@ import coil.disk.DiskCache
|
||||
import coil.memory.MemoryCache
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import me.rhunk.snapenhance.bridge.BridgeService
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.core.config.ModConfig
|
||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||
import me.rhunk.snapenhance.core.download.DownloadTaskManager
|
||||
import me.rhunk.snapenhance.messaging.ModDatabase
|
||||
import me.rhunk.snapenhance.messaging.StreaksReminder
|
||||
import me.rhunk.snapenhance.ui.manager.MainActivity
|
||||
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
|
||||
import me.rhunk.snapenhance.ui.manager.data.ModMappingsInfo
|
||||
import me.rhunk.snapenhance.ui.manager.data.ModInfo
|
||||
import me.rhunk.snapenhance.ui.manager.data.PlatformInfo
|
||||
import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo
|
||||
import me.rhunk.snapenhance.ui.setup.Requirements
|
||||
import me.rhunk.snapenhance.ui.setup.SetupActivity
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.lang.ref.WeakReference
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
|
||||
class RemoteSideContext(
|
||||
val androidContext: Context
|
||||
@ -42,6 +52,7 @@ class RemoteSideContext(
|
||||
val downloadTaskManager = DownloadTaskManager()
|
||||
val modDatabase = ModDatabase(this)
|
||||
val streaksReminder = StreaksReminder(this)
|
||||
val log = LogManager(this)
|
||||
|
||||
//used to load bitmoji selfies and download previews
|
||||
val imageLoader by lazy {
|
||||
@ -76,37 +87,57 @@ class RemoteSideContext(
|
||||
modDatabase.init()
|
||||
streaksReminder.init()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to load RemoteSideContext", it)
|
||||
log.error("Failed to load RemoteSideContext", it)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstallationSummary() = InstallationSummary(
|
||||
snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
|
||||
SnapchatAppInfo(
|
||||
version = it.versionName,
|
||||
versionCode = it.longVersionCode
|
||||
val installationSummary by lazy {
|
||||
InstallationSummary(
|
||||
snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
|
||||
SnapchatAppInfo(
|
||||
packageName = it.packageName,
|
||||
version = it.versionName,
|
||||
versionCode = it.longVersionCode,
|
||||
isLSPatched = it.applicationInfo.appComponentFactory != CoreComponentFactory::class.java.name,
|
||||
isSplitApk = it.splitNames.isNotEmpty()
|
||||
)
|
||||
},
|
||||
modInfo = ModInfo(
|
||||
loaderPackageName = MainActivity::class.java.`package`?.name ?: "unknown",
|
||||
buildPackageName = BuildConfig.APPLICATION_ID,
|
||||
buildVersion = BuildConfig.VERSION_NAME,
|
||||
buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
|
||||
buildIssuer = androidContext.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNING_CERTIFICATES)
|
||||
?.signingInfo?.apkContentsSigners?.firstOrNull()?.let {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val cert = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
|
||||
cert.issuerDN.toString()
|
||||
} ?: throw Exception("Failed to get certificate info"),
|
||||
isDebugBuild = BuildConfig.DEBUG,
|
||||
mappingVersion = mappings.getGeneratedBuildNumber(),
|
||||
mappingsOutdated = mappings.isMappingsOutdated()
|
||||
),
|
||||
platformInfo = PlatformInfo(
|
||||
device = Build.DEVICE,
|
||||
buildFingerprint = Build.FINGERPRINT,
|
||||
androidVersion = Build.VERSION.RELEASE,
|
||||
systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
|
||||
)
|
||||
},
|
||||
mappingsInfo = if (mappings.isMappingsLoaded()) {
|
||||
ModMappingsInfo(
|
||||
generatedSnapchatVersion = mappings.getGeneratedBuildNumber(),
|
||||
isOutdated = mappings.isMappingsOutdated()
|
||||
)
|
||||
} else null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun longToast(message: Any) {
|
||||
androidContext.mainExecutor.execute {
|
||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
Logger.debug(message.toString())
|
||||
log.debug(message.toString())
|
||||
}
|
||||
|
||||
fun shortToast(message: Any) {
|
||||
androidContext.mainExecutor.execute {
|
||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Logger.debug(message.toString())
|
||||
log.debug(message.toString())
|
||||
}
|
||||
|
||||
fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
|
||||
|
@ -3,16 +3,16 @@ package me.rhunk.snapenhance.bridge
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.LogLevel
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.SharedContextHolder
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.MessageLoggerWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.MessageLoggerWrapper
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.download.DownloadProcessor
|
||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||
import kotlin.system.measureTimeMillis
|
||||
@ -36,7 +36,7 @@ class BridgeService : Service() {
|
||||
fun triggerFriendSync(friendId: String) {
|
||||
val syncedFriend = syncCallback.syncFriend(friendId)
|
||||
if (syncedFriend == null) {
|
||||
Logger.error("Failed to sync friend $friendId")
|
||||
remoteSideContext.log.error("Failed to sync friend $friendId")
|
||||
return
|
||||
}
|
||||
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
|
||||
@ -47,7 +47,7 @@ class BridgeService : Service() {
|
||||
fun triggerGroupSync(groupId: String) {
|
||||
val syncedGroup = syncCallback.syncGroup(groupId)
|
||||
if (syncedGroup == null) {
|
||||
Logger.error("Failed to sync group $groupId")
|
||||
remoteSideContext.log.error("Failed to sync group $groupId")
|
||||
return
|
||||
}
|
||||
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
|
||||
@ -56,10 +56,12 @@ class BridgeService : Service() {
|
||||
}
|
||||
|
||||
inner class BridgeBinder : BridgeInterface.Stub() {
|
||||
override fun broadcastLog(tag: String, level: String, message: String) {
|
||||
remoteSideContext.log.internalLog(tag, LogLevel.fromShortName(level) ?: LogLevel.INFO, message)
|
||||
}
|
||||
|
||||
override fun fileOperation(action: Int, fileType: Int, content: ByteArray?): ByteArray {
|
||||
val resolvedFile by lazy {
|
||||
BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
|
||||
}
|
||||
val resolvedFile = BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
|
||||
|
||||
return when (FileActionType.values()[action]) {
|
||||
FileActionType.CREATE_AND_READ -> {
|
||||
@ -108,8 +110,6 @@ class BridgeService : Service() {
|
||||
override fun deleteMessageLoggerMessage(conversationId: String, id: Long) =
|
||||
messageLoggerWrapper.deleteMessage(conversationId, id)
|
||||
|
||||
override fun clearMessageLogger() = messageLoggerWrapper.clearMessages()
|
||||
|
||||
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
|
||||
|
||||
override fun fetchLocales(userLocale: String) =
|
||||
@ -133,25 +133,24 @@ class BridgeService : Service() {
|
||||
}
|
||||
|
||||
override fun sync(callback: SyncCallback) {
|
||||
Logger.debug("Syncing remote")
|
||||
syncCallback = callback
|
||||
measureTimeMillis {
|
||||
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
|
||||
runCatching {
|
||||
triggerFriendSync(friendId)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to sync friend $friendId", it)
|
||||
remoteSideContext.log.error("Failed to sync friend $friendId", it)
|
||||
}
|
||||
}
|
||||
remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId ->
|
||||
runCatching {
|
||||
triggerGroupSync(groupId)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to sync group $groupId", it)
|
||||
remoteSideContext.log.error("Failed to sync group $groupId", it)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
Logger.debug("Syncing remote took $it ms")
|
||||
remoteSideContext.log.verbose("Syncing remote took $it ms")
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +158,7 @@ class BridgeService : Service() {
|
||||
groups: List<String>,
|
||||
friends: List<String>
|
||||
) {
|
||||
Logger.debug("Received ${groups.size} groups and ${friends.size} friends")
|
||||
remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
|
||||
remoteSideContext.modDatabase.receiveMessagingDataCallback(
|
||||
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) },
|
||||
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) }
|
||||
|
@ -19,14 +19,15 @@ import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.download.data.DownloadRequest
|
||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
||||
import me.rhunk.snapenhance.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
|
||||
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||
import java.io.File
|
||||
@ -178,14 +179,14 @@ class DownloadProcessor (
|
||||
mediaScanIntent.setData(outputFile.uri)
|
||||
remoteSideContext.androidContext.sendBroadcast(mediaScanIntent)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to scan media file", it)
|
||||
remoteSideContext.log.error("Failed to scan media file", it)
|
||||
callbackOnFailure(translation.format("failed_gallery_toast", "error" to it.toString()), it.message)
|
||||
}
|
||||
|
||||
Logger.debug("download complete")
|
||||
remoteSideContext.log.verbose("download complete")
|
||||
callbackOnSuccess(fileName)
|
||||
}.onFailure { exception ->
|
||||
Logger.error(exception)
|
||||
remoteSideContext.log.error("Failed to save media to gallery", exception)
|
||||
callbackOnFailure(translation.format("failed_gallery_toast", "error" to exception.toString()), exception.message)
|
||||
downloadObject.downloadStage = DownloadStage.FAILED
|
||||
}
|
||||
@ -284,7 +285,7 @@ class DownloadProcessor (
|
||||
saveMediaToGallery(outputFile, downloadObjectObject)
|
||||
}.onFailure { exception ->
|
||||
if (coroutineContext.job.isCancelled) return@onFailure
|
||||
Logger.error(exception)
|
||||
remoteSideContext.log.error("Failed to download dash media", exception)
|
||||
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
|
||||
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
||||
}
|
||||
@ -333,7 +334,7 @@ class DownloadProcessor (
|
||||
val downloadedMedias = downloadInputMedias(downloadRequest).map {
|
||||
it.key to DownloadedFile(it.value, FileType.fromFile(it.value))
|
||||
}.toMap().toMutableMap()
|
||||
Logger.debug("downloaded ${downloadedMedias.size} medias")
|
||||
remoteSideContext.log.verbose("downloaded ${downloadedMedias.size} medias")
|
||||
|
||||
var shouldMergeOverlay = downloadRequest.shouldMergeOverlay
|
||||
|
||||
@ -376,7 +377,7 @@ class DownloadProcessor (
|
||||
saveMediaToGallery(mergedOverlay, downloadObjectObject)
|
||||
}.onFailure { exception ->
|
||||
if (coroutineContext.job.isCancelled) return@onFailure
|
||||
Logger.error(exception)
|
||||
remoteSideContext.log.error("Failed to merge overlay", exception)
|
||||
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
|
||||
downloadObjectObject.downloadStage = DownloadStage.MERGE_FAILED
|
||||
}
|
||||
@ -390,7 +391,7 @@ class DownloadProcessor (
|
||||
downloadRemoteMedia(downloadObjectObject, downloadedMedias, downloadRequest)
|
||||
}.onFailure { exception ->
|
||||
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
||||
Logger.error(exception)
|
||||
remoteSideContext.log.error("Failed to download media", exception)
|
||||
callbackOnFailure(translation["failed_generic_toast"], exception.message)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package me.rhunk.snapenhance.messaging
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.FriendStreaks
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLongOrNull
|
||||
@ -28,7 +27,7 @@ class ModDatabase(
|
||||
runCatching {
|
||||
block()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to execute async block", it)
|
||||
context.log.error("Failed to execute async block", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +102,7 @@ class ModDatabase(
|
||||
selfieId = cursor.getStringOrNull("selfieId")
|
||||
))
|
||||
}.onFailure {
|
||||
Logger.error("Failed to parse friend", it)
|
||||
context.log.error("Failed to parse friend", it)
|
||||
}
|
||||
}
|
||||
friends
|
||||
|
@ -13,7 +13,7 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.ui.manager.sections.HomeSection
|
||||
import me.rhunk.snapenhance.ui.manager.sections.home.HomeSection
|
||||
import me.rhunk.snapenhance.ui.manager.sections.NotImplemented
|
||||
import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection
|
||||
import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection
|
||||
@ -64,6 +64,8 @@ open class Section {
|
||||
lateinit var context: RemoteSideContext
|
||||
lateinit var navController: NavController
|
||||
|
||||
val currentRoute get() = navController.currentBackStackEntry?.destination?.route
|
||||
|
||||
open fun init() {}
|
||||
open fun onResumed() {}
|
||||
|
||||
|
@ -2,16 +2,33 @@ package me.rhunk.snapenhance.ui.manager.data
|
||||
|
||||
|
||||
data class SnapchatAppInfo(
|
||||
val packageName: String,
|
||||
val version: String,
|
||||
val versionCode: Long
|
||||
val versionCode: Long,
|
||||
val isLSPatched: Boolean,
|
||||
val isSplitApk: Boolean,
|
||||
)
|
||||
|
||||
data class ModMappingsInfo(
|
||||
val generatedSnapchatVersion: Long,
|
||||
val isOutdated: Boolean
|
||||
data class ModInfo(
|
||||
val loaderPackageName: String,
|
||||
val buildPackageName: String,
|
||||
val buildVersion: String,
|
||||
val buildVersionCode: Long,
|
||||
val buildIssuer: String,
|
||||
val isDebugBuild: Boolean,
|
||||
val mappingVersion: Long?,
|
||||
val mappingsOutdated: Boolean?,
|
||||
)
|
||||
|
||||
data class PlatformInfo(
|
||||
val device: String,
|
||||
val buildFingerprint: String,
|
||||
val androidVersion: String,
|
||||
val systemAbi: String,
|
||||
)
|
||||
|
||||
data class InstallationSummary(
|
||||
val platformInfo: PlatformInfo,
|
||||
val snapchatInfo: SnapchatAppInfo?,
|
||||
val mappingsInfo: ModMappingsInfo?
|
||||
val modInfo: ModInfo?,
|
||||
)
|
||||
|
@ -1,147 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui.manager.sections
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.rhunk.snapenhance.ui.manager.Section
|
||||
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
|
||||
import me.rhunk.snapenhance.ui.setup.Requirements
|
||||
import java.util.Locale
|
||||
|
||||
class HomeSection : Section() {
|
||||
companion object {
|
||||
val cardMargin = 10.dp
|
||||
}
|
||||
private val installationSummary = mutableStateOf(null as InstallationSummary?)
|
||||
private val userLocale = mutableStateOf(null as String?)
|
||||
|
||||
@Composable
|
||||
private fun SummaryCards(installationSummary: InstallationSummary) {
|
||||
//installation summary
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.padding(all = cardMargin)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column(modifier = Modifier.padding(all = 16.dp)) {
|
||||
if (installationSummary.snapchatInfo != null) {
|
||||
Text("Snapchat version: ${installationSummary.snapchatInfo.version}")
|
||||
Text("Snapchat version code: ${installationSummary.snapchatInfo.versionCode}")
|
||||
} else {
|
||||
Text("Snapchat not installed/detected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.padding(all = cardMargin)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Map,
|
||||
contentDescription = "Mappings",
|
||||
modifier = Modifier
|
||||
.padding(end = 10.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
Text(text = if (installationSummary.mappingsInfo == null || installationSummary.mappingsInfo.isOutdated) {
|
||||
"Mappings ${if (installationSummary.mappingsInfo == null) "not generated" else "outdated"}"
|
||||
} else {
|
||||
"Mappings version ${installationSummary.mappingsInfo.generatedSnapchatVersion}"
|
||||
}, modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
//inline button
|
||||
Button(onClick = {
|
||||
context.checkForRequirements(Requirements.MAPPINGS)
|
||||
}, modifier = Modifier.height(40.dp)) {
|
||||
Icon(Icons.Filled.Refresh, contentDescription = "Refresh")
|
||||
}
|
||||
}
|
||||
}
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.padding(all = cardMargin)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Language,
|
||||
contentDescription = "Language",
|
||||
modifier = Modifier
|
||||
.padding(end = 10.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Text(text = userLocale.value ?: "Unknown", modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
//inline button
|
||||
Button(onClick = {
|
||||
context.checkForRequirements(Requirements.LANGUAGE)
|
||||
}, modifier = Modifier.height(40.dp)) {
|
||||
Icon(Icons.Filled.OpenInNew, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumed() {
|
||||
if (!context.mappings.isMappingsLoaded()) {
|
||||
context.mappings.init(context.androidContext)
|
||||
}
|
||||
installationSummary.value = context.getInstallationSummary()
|
||||
userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault())
|
||||
}
|
||||
|
||||
override fun sectionTopBarName() = "SnapEnhance"
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
override fun Content() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(ScrollState(0))
|
||||
) {
|
||||
Text(
|
||||
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl.",
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
SummaryCards(installationSummary = installationSummary.value ?: return)
|
||||
}
|
||||
}
|
||||
}
|
@ -51,8 +51,8 @@ import androidx.compose.ui.unit.sp
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.ui.manager.Section
|
||||
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
||||
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
||||
|
@ -439,7 +439,7 @@ class FeaturesSection : Section() {
|
||||
|
||||
IconButton(onClick = {
|
||||
showSearchBar = showSearchBar.not()
|
||||
if (!showSearchBar && navController.currentBackStackEntry?.destination?.route == SEARCH_FEATURE_ROUTE) {
|
||||
if (!showSearchBar && currentRoute == SEARCH_FEATURE_ROUTE) {
|
||||
navController.navigate(MAIN_ROUTE)
|
||||
}
|
||||
}) {
|
||||
|
@ -0,0 +1,312 @@
|
||||
package me.rhunk.snapenhance.ui.manager.sections.home
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.filled.ReceiptLong
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navigation
|
||||
import me.rhunk.snapenhance.R
|
||||
import me.rhunk.snapenhance.ui.manager.Section
|
||||
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
|
||||
import me.rhunk.snapenhance.ui.setup.Requirements
|
||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||
import me.rhunk.snapenhance.ui.util.saveFile
|
||||
import java.util.Locale
|
||||
|
||||
class HomeSection : Section() {
|
||||
companion object {
|
||||
val cardMargin = 10.dp
|
||||
const val HOME_ROOT = "home_root"
|
||||
const val DEBUG_SECTION_ROUTE = "home_debug"
|
||||
const val LOGS_SECTION_ROUTE = "home_logs"
|
||||
}
|
||||
|
||||
private val installationSummary = mutableStateOf(null as InstallationSummary?)
|
||||
private val userLocale = mutableStateOf(null as String?)
|
||||
private val homeSubSection by lazy { HomeSubSection(context) }
|
||||
private lateinit var activityLauncherHelper: ActivityLauncherHelper
|
||||
|
||||
override fun init() {
|
||||
activityLauncherHelper = ActivityLauncherHelper(context.activity!!)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SummaryCardRow(icon: ImageVector? = null, title: String, action: @Composable () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
icon?.let {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(end = 10.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Text(text = title, modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Column {
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SummaryCards(installationSummary: InstallationSummary) {
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.padding(all = cardMargin)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
SummaryCardRow(
|
||||
icon = Icons.Filled.Map,
|
||||
title = if (installationSummary.modInfo == null || installationSummary.modInfo.mappingsOutdated == true) {
|
||||
"Mappings ${if (installationSummary.modInfo == null) "not generated" else "outdated"}"
|
||||
} else {
|
||||
"Mappings version ${installationSummary.modInfo.mappingVersion}"
|
||||
}
|
||||
) {
|
||||
Button(onClick = {
|
||||
context.checkForRequirements(Requirements.MAPPINGS)
|
||||
}, modifier = Modifier.height(40.dp)) {
|
||||
Icon(Icons.Filled.Refresh, contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
SummaryCardRow(icon = Icons.Filled.Language, title = userLocale.value ?: "Unknown") {
|
||||
Button(onClick = {
|
||||
context.checkForRequirements(Requirements.LANGUAGE)
|
||||
}, modifier = Modifier.height(40.dp)) {
|
||||
Icon(Icons.Filled.OpenInNew, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val summaryInfo = remember {
|
||||
mapOf(
|
||||
"Build Issuer" to (installationSummary.modInfo?.buildIssuer ?: "Unknown"),
|
||||
"Device" to installationSummary.platformInfo.device,
|
||||
"Android version" to installationSummary.platformInfo.androidVersion,
|
||||
"System ABI" to installationSummary.platformInfo.systemAbi,
|
||||
"Build fingerprint" to installationSummary.platformInfo.buildFingerprint
|
||||
)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(all = cardMargin)
|
||||
.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 10.dp),
|
||||
) {
|
||||
summaryInfo.forEach { (title, value) ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 5.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
)
|
||||
Text(
|
||||
fontSize = 14.sp,
|
||||
text = value,
|
||||
lineHeight = 20.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumed() {
|
||||
if (!context.mappings.isMappingsLoaded()) {
|
||||
context.mappings.init(context.androidContext)
|
||||
}
|
||||
installationSummary.value = context.installationSummary
|
||||
userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault())
|
||||
}
|
||||
|
||||
override fun sectionTopBarName(): String {
|
||||
if (currentRoute == HOME_ROOT) {
|
||||
return ""
|
||||
}
|
||||
return context.translation["manager.routes.$currentRoute"]
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun FloatingActionButton() {
|
||||
if (currentRoute == LOGS_SECTION_ROUTE) {
|
||||
homeSubSection.LogsActionButtons()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun TopBarActions(rowScope: RowScope) {
|
||||
rowScope.apply {
|
||||
when (currentRoute) {
|
||||
HOME_ROOT -> {
|
||||
IconButton(onClick = {
|
||||
navController.navigate(LOGS_SECTION_ROUTE)
|
||||
}) {
|
||||
Icon(Icons.Filled.ReceiptLong, contentDescription = null)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
navController.navigate(DEBUG_SECTION_ROUTE)
|
||||
}) {
|
||||
Icon(Icons.Filled.BugReport, contentDescription = null)
|
||||
}
|
||||
}
|
||||
LOGS_SECTION_ROUTE -> {
|
||||
var showDropDown by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(onClick = {
|
||||
showDropDown = true
|
||||
}) {
|
||||
Icon(Icons.Filled.MoreVert, contentDescription = null)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showDropDown,
|
||||
onDismissRequest = { showDropDown = false },
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
) {
|
||||
DropdownMenuItem(onClick = {
|
||||
context.log.clearLogs()
|
||||
navController.navigate(LOGS_SECTION_ROUTE)
|
||||
showDropDown = false
|
||||
}, text = {
|
||||
Text(text = "Clear logs")
|
||||
})
|
||||
|
||||
DropdownMenuItem(onClick = {
|
||||
val logFile = context.log.getLogFile()
|
||||
activityLauncherHelper.saveFile(logFile.name, "text/plain") { uri ->
|
||||
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
|
||||
logFile.inputStream().copyTo(it)
|
||||
context.longToast("Saved logs to $uri")
|
||||
}
|
||||
}
|
||||
showDropDown = false
|
||||
}, text = {
|
||||
Text(text = "Export logs")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(navGraphBuilder: NavGraphBuilder) {
|
||||
navGraphBuilder.navigation(
|
||||
route = enumSection.route,
|
||||
startDestination = HOME_ROOT
|
||||
) {
|
||||
composable(HOME_ROOT) {
|
||||
Content()
|
||||
}
|
||||
composable(LOGS_SECTION_ROUTE) {
|
||||
homeSubSection.LogsSection()
|
||||
}
|
||||
composable(DEBUG_SECTION_ROUTE) {
|
||||
homeSubSection.DebugSection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
override fun Content() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(ScrollState(0))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.launcher_icon_monochrome),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
|
||||
contentScale = ContentScale.FillHeight,
|
||||
modifier = Modifier
|
||||
.height(120.dp)
|
||||
.scale(1.75f)
|
||||
)
|
||||
Text(
|
||||
text = ("\u0065" + "\u0063" + "\u006e" + "\u0061" + "\u0068" + "\u006e" + "\u0045" + "\u0070" + "\u0061" + "\u006e" + "\u0053").reversed(),
|
||||
fontSize = 30.sp,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Text(
|
||||
text = "An xposed module that enhances the Snapchat experience",
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
SummaryCards(installationSummary = installationSummary.value ?: return)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package me.rhunk.snapenhance.ui.manager.sections.home
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardDoubleArrowUp
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.LogReader
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.action.EnumAction
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||
import me.rhunk.snapenhance.ui.util.AlertDialogs
|
||||
|
||||
class HomeSubSection(
|
||||
private val context: RemoteSideContext
|
||||
) {
|
||||
private val dialogs by lazy { AlertDialogs(context.translation) }
|
||||
|
||||
private lateinit var logListState: LazyListState
|
||||
|
||||
@Composable
|
||||
private fun RowAction(title: String, requireConfirmation: Boolean = false, action: () -> Unit) {
|
||||
var confirmationDialog by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
fun takeAction() {
|
||||
if (requireConfirmation) {
|
||||
confirmationDialog = true
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
if (requireConfirmation && confirmationDialog) {
|
||||
Dialog(onDismissRequest = { confirmationDialog = false }) {
|
||||
dialogs.ConfirmDialog(title = "Are you sure?", onConfirm = {
|
||||
action()
|
||||
confirmationDialog = false
|
||||
}, onDismiss = {
|
||||
confirmationDialog = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(65.dp)
|
||||
.clickable {
|
||||
takeAction()
|
||||
},
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text = title, modifier = Modifier.padding(start = 26.dp))
|
||||
IconButton(onClick = { takeAction() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.OpenInNew,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogsSection() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var lineCount by remember { mutableIntStateOf(0) }
|
||||
var logReader by remember { mutableStateOf<LogReader?>(null) }
|
||||
logListState = remember { LazyListState(0) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
state = logListState
|
||||
) {
|
||||
items(lineCount) { index ->
|
||||
val line = logReader?.getLogLine(index) ?: return@items
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
if (index % 2 == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant
|
||||
)) {
|
||||
Text(text = line.message, modifier = Modifier.padding(9.dp), fontSize = 10.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logReader == null) {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
logReader = context.log.newReader {
|
||||
lineCount++
|
||||
}
|
||||
lineCount = logReader!!.lineCount
|
||||
}.onFailure {
|
||||
context.longToast("Failed to read logs!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogsActionButtons() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
) {
|
||||
FilledIconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
logListState.scrollToItem(0)
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Filled.KeyboardDoubleArrowUp, contentDescription = null)
|
||||
}
|
||||
|
||||
FilledIconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
logListState.scrollToItem(logListState.layoutInfo.totalItemsCount - 1)
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Filled.KeyboardDoubleArrowDown, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchActionIntent(action: EnumAction) {
|
||||
val intent = context.androidContext.packageManager.getLaunchIntentForPackage(Constants.SNAPCHAT_PACKAGE_NAME)
|
||||
intent?.putExtra(ActionManager.ACTION_PARAMETER, action.key)
|
||||
context.androidContext.startActivity(intent)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowTitle(title: String) {
|
||||
Text(text = title, modifier = Modifier.padding(16.dp), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DebugSection() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(ScrollState(0))
|
||||
) {
|
||||
RowTitle(title = "Actions")
|
||||
EnumAction.values().forEach { enumAction ->
|
||||
RowAction(title = context.translation["actions.${enumAction.key}"]) {
|
||||
launchActionIntent(enumAction)
|
||||
}
|
||||
}
|
||||
|
||||
RowTitle(title = "Clear Files")
|
||||
BridgeFileType.values().forEach { fileType ->
|
||||
RowAction(title = fileType.displayName, requireConfirmation = true) {
|
||||
runCatching {
|
||||
fileType.resolve(context.androidContext).delete()
|
||||
context.longToast("Deleted ${fileType.displayName}!")
|
||||
}.onFailure {
|
||||
context.longToast("Failed to delete ${fileType.displayName}!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -43,9 +43,8 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||
@ -55,8 +54,8 @@ class AddFriendDialog(
|
||||
private val section: SocialSection,
|
||||
) {
|
||||
@Composable
|
||||
private fun ListCardEntry(name: String, currentState: () -> Boolean, onState: (Boolean) -> Unit = {}) {
|
||||
var currentState by remember { mutableStateOf(currentState()) }
|
||||
private fun ListCardEntry(name: String, getCurrentState: () -> Boolean, onState: (Boolean) -> Unit = {}) {
|
||||
var currentState by remember { mutableStateOf(getCurrentState()) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -74,7 +73,7 @@ class AddFriendDialog(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.onGloballyPositioned {
|
||||
currentState = currentState()
|
||||
currentState = getCurrentState()
|
||||
}
|
||||
)
|
||||
|
||||
@ -149,7 +148,7 @@ class AddFriendDialog(
|
||||
runCatching {
|
||||
context.androidContext.sendBroadcast(it)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to send broadcast", it)
|
||||
context.log.error("Failed to send broadcast", it)
|
||||
hasFetchError = true
|
||||
}
|
||||
}
|
||||
@ -234,7 +233,7 @@ class AddFriendDialog(
|
||||
val group = filteredGroups[it]
|
||||
ListCardEntry(
|
||||
name = group.name,
|
||||
currentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
|
||||
getCurrentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
|
||||
) { state ->
|
||||
if (state) {
|
||||
context.bridgeService.triggerGroupSync(group.conversationId)
|
||||
@ -261,7 +260,7 @@ class AddFriendDialog(
|
||||
|
||||
ListCardEntry(
|
||||
name = friend.displayName?.takeIf { name -> name.isNotBlank() } ?: friend.mutableUsername,
|
||||
currentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
|
||||
getCurrentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
|
||||
) { state ->
|
||||
if (state) {
|
||||
context.bridgeService.triggerFriendSync(friend.userId)
|
||||
|
@ -79,7 +79,7 @@ class SocialSection : Section() {
|
||||
groupList = context.modDatabase.getGroups()
|
||||
}
|
||||
|
||||
override fun canGoBack() = navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE
|
||||
override fun canGoBack() = currentRoute != MAIN_ROUTE
|
||||
|
||||
override fun build(navGraphBuilder: NavGraphBuilder) {
|
||||
navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) {
|
||||
@ -117,7 +117,7 @@ class SocialSection : Section() {
|
||||
}
|
||||
}
|
||||
|
||||
if (navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE) {
|
||||
if (currentRoute != MAIN_ROUTE) {
|
||||
IconButton(
|
||||
onClick = { deleteConfirmDialog = true },
|
||||
) {
|
||||
|
@ -44,7 +44,7 @@ class MappingsScreen : SetupScreen() {
|
||||
|
||||
fun tryToGenerateMappings() {
|
||||
//check for snapchat installation
|
||||
val installationSummary = context.getInstallationSummary()
|
||||
val installationSummary = context.installationSummary
|
||||
if (installationSummary.snapchatInfo == null) {
|
||||
throw Exception(context.translation["setup.mappings.generate_failure_no_snapchat"])
|
||||
}
|
||||
@ -69,7 +69,7 @@ class MappingsScreen : SetupScreen() {
|
||||
}.onFailure {
|
||||
isGenerating = false
|
||||
infoText = context.translation["setup.mappings.generate_failure"] + "\n\n" + it.message
|
||||
Logger.error("Failed to generate mappings", it)
|
||||
context.log.error("Failed to generate mappings", it)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
|
@ -25,7 +25,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
||||
import java.util.Locale
|
||||
|
@ -7,7 +7,6 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
||||
@ -23,7 +22,6 @@ class SaveFolderScreen : SetupScreen() {
|
||||
saveFolder = ObservableMutableState(
|
||||
defaultValue = "",
|
||||
onChange = { _, newValue ->
|
||||
Logger.debug(newValue)
|
||||
if (newValue.isNotBlank()) {
|
||||
context.config.root.downloader.saveFolder.set(newValue)
|
||||
context.config.writeConfig()
|
||||
|
@ -16,7 +16,7 @@ class ActivityLauncherHelper(
|
||||
runCatching {
|
||||
callback?.let { it(result.data!!) }
|
||||
}.onFailure {
|
||||
Logger.error("Failed to process activity result", it)
|
||||
Logger.directError("Failed to process activity result", it)
|
||||
}
|
||||
}
|
||||
callback = null
|
||||
|
@ -33,7 +33,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.config.DataProcessors
|
||||
import me.rhunk.snapenhance.core.config.PropertyPair
|
||||
|
||||
|
@ -5,53 +5,47 @@ import me.rhunk.snapenhance.bridge.DownloadCallback;
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback;
|
||||
|
||||
interface BridgeInterface {
|
||||
/**
|
||||
* broadcast a log message
|
||||
*/
|
||||
void broadcastLog(String tag, String level, String message);
|
||||
|
||||
/**
|
||||
* Execute a file operation
|
||||
* @param fileType the corresponding file type (see BridgeFileType)
|
||||
*/
|
||||
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
||||
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param conversationId the ID of the conversation
|
||||
* @return the content of the message
|
||||
* @return message ids that are logged
|
||||
*/
|
||||
long[] getLoggedMessageIds(String conversationId, int limit);
|
||||
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @return the content of the message
|
||||
*/
|
||||
@nullable byte[] getMessageLoggerMessage(String conversationId, long id);
|
||||
|
||||
/**
|
||||
* Add a message to the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @param message the content of the message
|
||||
*/
|
||||
void addMessageLoggerMessage(String conversationId, long id, in byte[] message);
|
||||
|
||||
/**
|
||||
* Delete a message from the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
*/
|
||||
void deleteMessageLoggerMessage(String conversationId, long id);
|
||||
|
||||
/**
|
||||
* Clear the message logger database
|
||||
*/
|
||||
void clearMessageLogger();
|
||||
|
||||
* Get the application APK path (assets for the conversation exporter)
|
||||
*/
|
||||
String getApplicationApkPath();
|
||||
|
||||
/**
|
||||
* Fetch the locales
|
||||
*
|
||||
* @return the locale result
|
||||
* @return the map of locales (key: locale short name, value: locale data as json)
|
||||
*/
|
||||
Map<String, String> fetchLocales(String userLocale);
|
||||
|
||||
@ -62,11 +56,14 @@ interface BridgeInterface {
|
||||
|
||||
/**
|
||||
* Get rules for a given user or conversation
|
||||
* @return list of rules (MessagingRuleType)
|
||||
*/
|
||||
List<String> getRules(String uuid);
|
||||
|
||||
/**
|
||||
* Update rule for a giver user or conversation
|
||||
*
|
||||
* @param type rule type (MessagingRuleType)
|
||||
*/
|
||||
void setRule(String uuid, String type, boolean state);
|
||||
|
||||
@ -77,8 +74,8 @@ interface BridgeInterface {
|
||||
|
||||
/**
|
||||
* Pass all groups and friends to be able to add them to the database
|
||||
* @param groups serialized groups
|
||||
* @param friends serialized friends
|
||||
* @param groups list of groups (MessagingGroupInfo as json string)
|
||||
* @param friends list of friends (MessagingFriendInfo as json string)
|
||||
*/
|
||||
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
|
||||
}
|
@ -20,6 +20,8 @@
|
||||
"downloads": "Downloads",
|
||||
"features": "Features",
|
||||
"home": "Home",
|
||||
"home_debug": "Debug",
|
||||
"home_logs": "Logs",
|
||||
"social": "Social",
|
||||
"plugins": "Plugins"
|
||||
},
|
||||
@ -64,8 +66,8 @@
|
||||
}
|
||||
},
|
||||
|
||||
"action": {
|
||||
"clean_cache": "Clean Cache",
|
||||
"actions": {
|
||||
"clean_snapchat_cache": "Clean Snapchat Cache",
|
||||
"clear_message_logger": "Clear Message Logger",
|
||||
"refresh_mappings": "Refresh Mappings",
|
||||
"open_map": "Choose location on map",
|
||||
|
@ -1,14 +1,12 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
object Constants {
|
||||
const val TAG = "SnapEnhance"
|
||||
const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android"
|
||||
|
||||
const val VIEW_INJECTED_CODE = 0x7FFFFF02
|
||||
|
||||
val ARROYO_MEDIA_CONTAINER_PROTO_PATH = intArrayOf(4, 4)
|
||||
val ARROYO_STRING_CHAT_MESSAGE_PROTO = ARROYO_MEDIA_CONTAINER_PROTO_PATH + intArrayOf(2, 1)
|
||||
val ARROYO_URL_KEY_PROTO_PATH = intArrayOf(4, 5, 1, 3)
|
||||
|
||||
const val ENCRYPTION_PROTO_INDEX = 19
|
||||
const val ENCRYPTION_PROTO_INDEX_V2 = 4
|
||||
|
@ -2,47 +2,81 @@ package me.rhunk.snapenhance
|
||||
|
||||
import android.util.Log
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
|
||||
object Logger {
|
||||
private const val TAG = "SnapEnhance"
|
||||
enum class LogLevel(
|
||||
val letter: String,
|
||||
val shortName: String,
|
||||
val priority: Int = Log.INFO
|
||||
) {
|
||||
VERBOSE("V", "verbose", Log.VERBOSE),
|
||||
DEBUG("D", "debug", Log.DEBUG),
|
||||
INFO("I", "info", Log.INFO),
|
||||
WARN("W", "warn", Log.WARN),
|
||||
ERROR("E", "error", Log.ERROR),
|
||||
ASSERT("A", "assert", Log.ASSERT);
|
||||
|
||||
fun log(message: Any?) {
|
||||
Log.i(TAG, message.toString())
|
||||
companion object {
|
||||
fun fromLetter(letter: String): LogLevel? {
|
||||
return values().find { it.letter == letter }
|
||||
}
|
||||
|
||||
fun fromShortName(shortName: String): LogLevel? {
|
||||
return values().find { it.shortName == shortName }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Logger(
|
||||
private val bridgeClient: BridgeClient
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "SnapEnhanceCore"
|
||||
|
||||
fun directDebug(message: Any?, tag: String = TAG) {
|
||||
Log.println(Log.DEBUG, tag, message.toString())
|
||||
}
|
||||
|
||||
fun directError(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
Log.println(Log.ERROR, tag, message.toString())
|
||||
Log.println(Log.ERROR, tag, throwable.toString())
|
||||
}
|
||||
|
||||
fun xposedLog(message: Any?, tag: String = TAG) {
|
||||
Log.println(Log.INFO, tag, message.toString())
|
||||
XposedBridge.log("$tag: $message")
|
||||
}
|
||||
|
||||
fun xposedLog(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
Log.println(Log.INFO, tag, message.toString())
|
||||
XposedBridge.log("$tag: $message")
|
||||
XposedBridge.log(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
fun debug(message: Any?) {
|
||||
if (!BuildConfig.DEBUG) return
|
||||
Log.d(TAG, message.toString())
|
||||
private fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
|
||||
runCatching {
|
||||
bridgeClient.broadcastLog(tag, logLevel.shortName, message.toString())
|
||||
}.onFailure {
|
||||
Log.println(logLevel.priority, tag, message.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun debug(tag: String, message: Any?) {
|
||||
if (!BuildConfig.DEBUG) return
|
||||
Log.d(tag, message.toString())
|
||||
fun debug(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.DEBUG, message)
|
||||
|
||||
fun error(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ERROR, message)
|
||||
|
||||
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
internalLog(tag, LogLevel.ERROR, throwable)
|
||||
}
|
||||
|
||||
fun error(throwable: Throwable) {
|
||||
Log.e(TAG, "", throwable)
|
||||
}
|
||||
fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message)
|
||||
|
||||
fun error(message: Any?) {
|
||||
Log.e(TAG, message.toString())
|
||||
}
|
||||
fun verbose(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.VERBOSE, message)
|
||||
|
||||
fun error(message: Any?, throwable: Throwable) {
|
||||
Log.e(TAG, message.toString(), throwable)
|
||||
}
|
||||
fun warn(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.WARN, message)
|
||||
|
||||
fun xposedLog(message: Any?) {
|
||||
XposedBridge.log(message.toString())
|
||||
}
|
||||
|
||||
fun xposedLog(message: Any?, throwable: Throwable?) {
|
||||
XposedBridge.log(message.toString())
|
||||
XposedBridge.log(throwable)
|
||||
}
|
||||
|
||||
fun xposedLog(throwable: Throwable) {
|
||||
XposedBridge.log(throwable)
|
||||
}
|
||||
fun assert(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ASSERT, message)
|
||||
}
|
@ -11,13 +11,13 @@ import android.widget.Toast
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
|
||||
import me.rhunk.snapenhance.core.config.ModConfig
|
||||
import me.rhunk.snapenhance.core.database.DatabaseAccess
|
||||
import me.rhunk.snapenhance.core.eventbus.EventBus
|
||||
import me.rhunk.snapenhance.data.MessageSender
|
||||
import me.rhunk.snapenhance.database.DatabaseAccess
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
||||
@ -44,6 +44,7 @@ class ModContext {
|
||||
|
||||
private val modConfig = ModConfig()
|
||||
val config by modConfig
|
||||
val log by lazy { Logger(this.bridgeClient) }
|
||||
val event = EventBus(this)
|
||||
val eventDispatcher = EventDispatcher(this)
|
||||
val native = NativeLib()
|
||||
@ -81,13 +82,13 @@ class ModContext {
|
||||
}
|
||||
}
|
||||
|
||||
fun shortToast(message: Any) {
|
||||
fun shortToast(message: Any?) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun longToast(message: Any) {
|
||||
fun longToast(message: Any?) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
@ -108,7 +109,7 @@ class ModContext {
|
||||
}
|
||||
|
||||
fun crash(message: String, throwable: Throwable? = null) {
|
||||
Logger.xposedLog(message, throwable)
|
||||
Logger.xposedLog(message, throwable ?: Exception())
|
||||
longToast(message)
|
||||
delayForceCloseApp(100)
|
||||
}
|
||||
@ -123,6 +124,7 @@ class ModContext {
|
||||
}
|
||||
|
||||
fun reloadConfig() {
|
||||
log.verbose("reloading config")
|
||||
modConfig.loadFromBridge(bridgeClient)
|
||||
native.loadNativeConfig(
|
||||
NativeConfig(
|
||||
|
@ -6,9 +6,9 @@ import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
@ -88,7 +88,7 @@ class SnapEnhance {
|
||||
return@hook
|
||||
}
|
||||
|
||||
Logger.debug("Reloading config")
|
||||
appContext.actionManager.onNewIntent(activity.intent)
|
||||
appContext.reloadConfig()
|
||||
syncRemote()
|
||||
}
|
||||
@ -114,7 +114,7 @@ class SnapEnhance {
|
||||
syncRemote()
|
||||
}
|
||||
}.also { time ->
|
||||
Logger.debug("init took $time")
|
||||
appContext.log.verbose("init took $time")
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ class SnapEnhance {
|
||||
actionManager.init()
|
||||
}
|
||||
}.also { time ->
|
||||
Logger.debug("onActivityCreate took $time")
|
||||
appContext.log.verbose("onActivityCreate took $time")
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,6 @@ class SnapEnhance {
|
||||
val database = appContext.database
|
||||
|
||||
appContext.executeAsync {
|
||||
Logger.debug("request remote sync")
|
||||
appContext.bridgeClient.sync(object : SyncCallback.Stub() {
|
||||
override fun syncFriend(uuid: String): String? {
|
||||
return database.getFriendInfo(uuid)?.toJson()
|
||||
|
@ -3,16 +3,9 @@ package me.rhunk.snapenhance.action
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractAction(
|
||||
val nameKey: String
|
||||
) {
|
||||
abstract class AbstractAction{
|
||||
lateinit var context: ModContext
|
||||
|
||||
/**
|
||||
* called on the main thread when the mod initialize
|
||||
*/
|
||||
open fun init() {}
|
||||
|
||||
/**
|
||||
* called when the action is triggered
|
||||
*/
|
||||
|
@ -0,0 +1,17 @@
|
||||
package me.rhunk.snapenhance.action
|
||||
|
||||
import me.rhunk.snapenhance.action.impl.CleanCache
|
||||
import me.rhunk.snapenhance.action.impl.ExportChatMessages
|
||||
import me.rhunk.snapenhance.action.impl.OpenMap
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class EnumAction(
|
||||
val key: String,
|
||||
val clazz: KClass<out AbstractAction>,
|
||||
val exitOnFinish: Boolean = false,
|
||||
val isCritical: Boolean = false,
|
||||
) {
|
||||
CLEAN_CACHE("clean_snapchat_cache", CleanCache::class, exitOnFinish = true),
|
||||
EXPORT_CHAT_MESSAGES("export_chat_messages", ExportChatMessages::class),
|
||||
OPEN_MAP("open_map", OpenMap::class);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package me.rhunk.snapenhance.action.impl
|
||||
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.features.impl.AutoUpdater
|
||||
|
||||
class CheckForUpdates : AbstractAction("action.check_for_updates") {
|
||||
override fun run() {
|
||||
context.executeAsync {
|
||||
runCatching {
|
||||
val latestVersion = context.feature(AutoUpdater::class).checkForUpdates()
|
||||
if (latestVersion == null) {
|
||||
context.longToast(context.translation["auto_updater.no_update_available"])
|
||||
}
|
||||
}.onFailure {
|
||||
context.longToast(it.message ?: "Failed to check for updates")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package me.rhunk.snapenhance.action.impl
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import java.io.File
|
||||
|
||||
class CleanCache : AbstractAction("action.clean_cache") {
|
||||
class CleanCache : AbstractAction() {
|
||||
companion object {
|
||||
private val FILES = arrayOf(
|
||||
"files/mbgl-offline.db",
|
||||
@ -22,7 +22,7 @@ class CleanCache : AbstractAction("action.clean_cache") {
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
FILES.forEach {fileName ->
|
||||
FILES.forEach { fileName ->
|
||||
val fileCache = File(context.androidContext.dataDir, fileName)
|
||||
if (fileName.endsWith("*")) {
|
||||
val parent = fileCache.parentFile ?: throw IllegalStateException("Parent file is null")
|
||||
|
@ -1,10 +0,0 @@
|
||||
package me.rhunk.snapenhance.action.impl
|
||||
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
|
||||
class ClearMessageLogger : AbstractAction("action.clear_message_logger") {
|
||||
override fun run() {
|
||||
context.bridgeClient.clearMessageLogger()
|
||||
context.shortToast("Message logger cleared")
|
||||
}
|
||||
}
|
@ -14,10 +14,10 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.util.CallbackBuilder
|
||||
@ -26,7 +26,7 @@ import me.rhunk.snapenhance.util.export.MessageExporter
|
||||
import java.io.File
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
class ExportChatMessages : AbstractAction() {
|
||||
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
||||
|
||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
||||
@ -55,7 +55,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
context.runOnUiThread {
|
||||
if (dialogLogs.size > 15) dialogLogs.removeAt(0)
|
||||
dialogLogs.add(message)
|
||||
Logger.debug("dialog: $message")
|
||||
context.log.debug("dialog: $message")
|
||||
currentActionDialog!!.setMessage(dialogLogs.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
@ -198,7 +198,6 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Logger.debug("[$conversationName] fetching $lastMessageId")
|
||||
val messages = fetchMessagesPaginated(conversationId, lastMessageId)
|
||||
if (messages.isEmpty()) break
|
||||
foundMessages.addAll(messages)
|
||||
@ -224,7 +223,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
it.readMessages(foundMessages)
|
||||
}.onFailure {
|
||||
logDialog(context.translation.format("chat_export.export_failed","conversation" to it.message.toString()))
|
||||
Logger.error(it)
|
||||
context.log.error("Failed to read messages", it)
|
||||
return
|
||||
}
|
||||
}.exportTo(exportType!!)
|
||||
|
@ -5,7 +5,7 @@ import android.os.Bundle
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
|
||||
class OpenMap: AbstractAction("action.open_map") {
|
||||
class OpenMap: AbstractAction() {
|
||||
override fun run() {
|
||||
context.runOnUiThread {
|
||||
val mapActivityIntent = Intent()
|
||||
|
@ -1,11 +0,0 @@
|
||||
package me.rhunk.snapenhance.action.impl
|
||||
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
|
||||
class RefreshMappings : AbstractAction("action.refresh_mappings") {
|
||||
override fun run() {
|
||||
context.bridgeClient.deleteFile(BridgeFileType.MAPPINGS)
|
||||
context.softRestartApp()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.bridge
|
||||
package me.rhunk.snapenhance.core.bridge
|
||||
|
||||
|
||||
import android.content.ComponentName
|
||||
@ -10,11 +10,13 @@ import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.IBinder
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.Logger.xposedLog
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.bridge.BridgeInterface
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||
import me.rhunk.snapenhance.data.LocalePair
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -29,7 +31,7 @@ class BridgeClient(
|
||||
private lateinit var service: BridgeInterface
|
||||
|
||||
companion object {
|
||||
const val BRIDGE_SYNC_ACTION = "me.rhunk.snapenhance.bridge.SYNC"
|
||||
const val BRIDGE_SYNC_ACTION = "me.rhunk.snapenhance.core.bridge.SYNC"
|
||||
}
|
||||
|
||||
fun start(callback: (Boolean) -> Unit) {
|
||||
@ -76,7 +78,7 @@ class BridgeClient(
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName) {
|
||||
xposedLog("failed to connect to bridge service")
|
||||
context.log.error("BridgeClient", "failed to connect to bridge service")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
@ -84,6 +86,8 @@ class BridgeClient(
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun broadcastLog(tag: String, level: String, message: String) = service.broadcastLog(tag, level, message)
|
||||
|
||||
fun createAndReadFile(
|
||||
fileType: BridgeFileType,
|
||||
defaultContent: ByteArray
|
||||
@ -108,8 +112,6 @@ class BridgeClient(
|
||||
|
||||
fun deleteMessageLoggerMessage(conversationId: String, id: Long) = service.deleteMessageLoggerMessage(conversationId, id)
|
||||
|
||||
fun clearMessageLogger() = service.clearMessageLogger()
|
||||
|
||||
fun fetchLocales(userLocale: String) = service.fetchLocales(userLocale).map {
|
||||
LocalePair(it.key, it.value)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package me.rhunk.snapenhance.bridge
|
||||
package me.rhunk.snapenhance.core.bridge
|
||||
|
||||
import android.content.Context
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
|
||||
open class FileLoaderWrapper(
|
||||
private val fileType: BridgeFileType,
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.bridge.types
|
||||
package me.rhunk.snapenhance.core.bridge.types
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.bridge.types
|
||||
package me.rhunk.snapenhance.core.bridge.types
|
||||
|
||||
enum class FileActionType {
|
||||
CREATE_AND_READ, READ, WRITE, DELETE, EXISTS
|
@ -1,10 +1,10 @@
|
||||
package me.rhunk.snapenhance.bridge.wrapper
|
||||
package me.rhunk.snapenhance.core.bridge.wrapper
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.data.LocalePair
|
||||
import java.util.Locale
|
||||
|
||||
@ -80,7 +80,7 @@ class LocaleWrapper {
|
||||
loadFromContext(context)
|
||||
}
|
||||
|
||||
operator fun get(key: String) = translationMap[key] ?: key.also { Logger.debug("Missing translation for $key") }
|
||||
operator fun get(key: String) = translationMap[key] ?: key.also { Logger.directDebug("Missing translation for $key") }
|
||||
|
||||
fun format(key: String, vararg args: Pair<String, String>): String {
|
||||
return args.fold(get(key)) { acc, pair ->
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.bridge.wrapper
|
||||
package me.rhunk.snapenhance.core.bridge.wrapper
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
@ -6,8 +6,8 @@ import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapmapper.Mapper
|
||||
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
||||
import me.rhunk.snapmapper.impl.CallbackMapper
|
||||
@ -58,11 +58,8 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
runCatching {
|
||||
loadCached()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to load cached mappings", it)
|
||||
delete()
|
||||
}
|
||||
} else {
|
||||
Logger.debug("Mappings file does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +119,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
}
|
||||
write(result.toString().toByteArray())
|
||||
}.also {
|
||||
Logger.debug("Generated mappings in $it ms")
|
||||
Logger.directDebug("Generated mappings in $it ms")
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.bridge.wrapper
|
||||
package me.rhunk.snapenhance.core.bridge.wrapper
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
@ -1,6 +1,6 @@
|
||||
package me.rhunk.snapenhance.core.config
|
||||
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
|
@ -4,11 +4,10 @@ import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.config.impl.RootConfig
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@ -33,7 +32,6 @@ class ModConfig {
|
||||
runCatching {
|
||||
loadConfig()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to load config", it)
|
||||
writeConfig()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package me.rhunk.snapenhance.core.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||
import me.rhunk.snapenhance.core.config.FeatureNotice
|
||||
|
||||
class Experimental : ConfigContainer() {
|
||||
val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" }
|
||||
@ -9,6 +10,6 @@ class Experimental : ConfigContainer() {
|
||||
val appLockOnResume = boolean("app_lock_on_resume")
|
||||
val infiniteStoryBoost = boolean("infinite_story_boost")
|
||||
val meoPasscodeBypass = boolean("meo_passcode_bypass")
|
||||
val unlimitedMultiSnap = boolean("unlimited_multi_snap")
|
||||
val unlimitedMultiSnap = boolean("unlimited_multi_snap") { addNotices(FeatureNotice.MAY_BAN)}
|
||||
val noFriendScoreDelay = boolean("no_friend_score_delay")
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package me.rhunk.snapenhance.database
|
||||
package me.rhunk.snapenhance.core.database
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.database.objects.*
|
||||
import me.rhunk.snapenhance.core.database.objects.ConversationMessage
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.database.objects.StoryEntry
|
||||
import me.rhunk.snapenhance.core.database.objects.UserConversationLink
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import java.io.File
|
||||
|
||||
@ -68,7 +72,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
||||
try {
|
||||
obj.write(cursor)
|
||||
} catch (e: Throwable) {
|
||||
Logger.xposedLog(e)
|
||||
context.log.error("Failed to read database object", e)
|
||||
}
|
||||
cursor.close()
|
||||
return obj
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.database
|
||||
package me.rhunk.snapenhance.core.database
|
||||
|
||||
import android.database.Cursor
|
||||
|
@ -1,10 +1,10 @@
|
||||
package me.rhunk.snapenhance.database.objects
|
||||
package me.rhunk.snapenhance.core.database.objects
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.ktx.getBlobOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
@ -1,8 +1,8 @@
|
||||
package me.rhunk.snapenhance.database.objects
|
||||
package me.rhunk.snapenhance.core.database.objects
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
@ -1,8 +1,8 @@
|
||||
package me.rhunk.snapenhance.database.objects
|
||||
package me.rhunk.snapenhance.core.database.objects
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
@ -1,8 +1,8 @@
|
||||
package me.rhunk.snapenhance.database.objects
|
||||
package me.rhunk.snapenhance.core.database.objects
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
@ -1,8 +1,8 @@
|
||||
package me.rhunk.snapenhance.database.objects
|
||||
package me.rhunk.snapenhance.core.database.objects
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
@ -1,15 +1,15 @@
|
||||
package me.rhunk.snapenhance.download
|
||||
package me.rhunk.snapenhance.core.download
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.download.data.DashOptions
|
||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.download.data.DownloadRequest
|
||||
import me.rhunk.snapenhance.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
|
||||
import me.rhunk.snapenhance.core.download.data.DashOptions
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
||||
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
|
||||
|
||||
class DownloadManagerClient (
|
||||
private val context: ModContext,
|
||||
@ -33,10 +33,12 @@ class DownloadManagerClient (
|
||||
fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) {
|
||||
enqueueDownloadRequest(
|
||||
DownloadRequest(
|
||||
inputMedias = arrayOf(InputMedia(
|
||||
inputMedias = arrayOf(
|
||||
InputMedia(
|
||||
content = playlistUrl,
|
||||
type = DownloadMediaType.REMOTE_MEDIA
|
||||
)),
|
||||
)
|
||||
),
|
||||
dashOptions = DashOptions(offsetTime, duration),
|
||||
flags = DownloadRequest.Flags.IS_DASH_PLAYLIST
|
||||
)
|
||||
@ -46,11 +48,13 @@ class DownloadManagerClient (
|
||||
fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) {
|
||||
enqueueDownloadRequest(
|
||||
DownloadRequest(
|
||||
inputMedias = arrayOf(InputMedia(
|
||||
inputMedias = arrayOf(
|
||||
InputMedia(
|
||||
content = mediaData,
|
||||
type = mediaType,
|
||||
encryption = encryption
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package me.rhunk.snapenhance.download
|
||||
package me.rhunk.snapenhance.core.download
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
import android.net.Uri
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
data class DownloadMetadata(
|
||||
val mediaIdentifier: String?,
|
@ -1,7 +1,7 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||
import me.rhunk.snapenhance.core.download.DownloadTaskManager
|
||||
|
||||
data class DownloadObject(
|
||||
var downloadId: Int = 0,
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
|
||||
data class DashOptions(val offsetTime: Long, val duration: Long?)
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
enum class DownloadStage(
|
||||
val isFinalStage: Boolean = false,
|
@ -1,6 +1,6 @@
|
||||
@file:OptIn(ExperimentalEncodingApi::class)
|
||||
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.media.EncryptionWrapper
|
||||
import kotlin.io.encoding.Base64
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
enum class MediaFilter(
|
||||
val key: String,
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.data
|
||||
package me.rhunk.snapenhance.core.download.data
|
||||
|
||||
enum class SplitMediaAssetType {
|
||||
ORIGINAL, OVERLAY
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.core.eventbus
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@ -14,7 +13,7 @@ interface IListener<T> {
|
||||
}
|
||||
|
||||
class EventBus(
|
||||
private val context: ModContext
|
||||
val context: ModContext
|
||||
) {
|
||||
private val subscribers = mutableMapOf<KClass<out Event>, MutableList<IListener<out Event>>>()
|
||||
|
||||
@ -34,7 +33,7 @@ class EventBus(
|
||||
runCatching {
|
||||
listener(event)
|
||||
}.onFailure {
|
||||
Logger.error("Error while handling event ${event::class.simpleName}", it)
|
||||
context.log.error("Error while handling event ${event::class.simpleName}", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,7 +60,7 @@ class EventBus(
|
||||
runCatching {
|
||||
(listener as IListener<T>).handle(event)
|
||||
}.onFailure { t ->
|
||||
Logger.error("Error while handling event ${event::class.simpleName} by ${listener::class.simpleName}", t)
|
||||
context.log.error("Error while handling event ${event::class.simpleName} by ${listener::class.simpleName}", t)
|
||||
}
|
||||
}
|
||||
return event
|
||||
|
@ -1,6 +1,6 @@
|
||||
package me.rhunk.snapenhance.features
|
||||
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
|
@ -10,7 +10,6 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
@ -36,7 +35,7 @@ class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVI
|
||||
runCatching {
|
||||
checkForUpdates()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to check for updates: ${it.message}", it)
|
||||
context.log.error("Failed to check for updates: ${it.message}", it)
|
||||
}.onSuccess {
|
||||
context.bridgeClient.setAutoUpdaterTime(currentTimeMillis)
|
||||
}
|
||||
|
@ -6,9 +6,15 @@ import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.widget.ImageView
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.Logger.xposedLog
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.core.download.data.toKeyPair
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
@ -17,14 +23,6 @@ import me.rhunk.snapenhance.data.wrapper.impl.media.dash.LongformVideoPlaylistIt
|
||||
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.DownloadManagerClient
|
||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.download.data.toKeyPair
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
@ -84,19 +82,19 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
callback = object: DownloadCallback.Stub() {
|
||||
override fun onSuccess(outputFile: String) {
|
||||
if (!downloadLogging.contains("success")) return
|
||||
Logger.debug("onSuccess: outputFile=$outputFile")
|
||||
context.log.verbose("onSuccess: outputFile=$outputFile")
|
||||
context.shortToast(context.translation.format("download_processor.saved_toast", "path" to outputFile.split("/").takeLast(2).joinToString("/")))
|
||||
}
|
||||
|
||||
override fun onProgress(message: String) {
|
||||
if (!downloadLogging.contains("progress")) return
|
||||
Logger.debug("onProgress: message=$message")
|
||||
context.log.verbose("onProgress: message=$message")
|
||||
context.shortToast(message)
|
||||
}
|
||||
|
||||
override fun onFailure(message: String, throwable: String?) {
|
||||
if (!downloadLogging.contains("failure")) return
|
||||
Logger.debug("onFailure: message=$message, throwable=$throwable")
|
||||
context.log.verbose("onFailure: message=$message, throwable=$throwable")
|
||||
throwable?.let {
|
||||
context.longToast((message + it.takeIf { it.isNotEmpty() }.orEmpty()))
|
||||
return
|
||||
@ -402,8 +400,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
try {
|
||||
handleOperaMedia(mediaParamMap, mediaInfoMap, false)
|
||||
} catch (e: Throwable) {
|
||||
xposedLog(e)
|
||||
context.longToast(e.message!!)
|
||||
context.log.error("Failed to handle opera media", e)
|
||||
context.longToast(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -524,11 +522,11 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
}
|
||||
}.onFailure {
|
||||
context.shortToast(translations["failed_to_create_preview_toast"])
|
||||
xposedLog(it)
|
||||
context.log.error("Failed to create preview", it)
|
||||
}
|
||||
}.onFailure {
|
||||
context.longToast(translations["failed_generic_toast"])
|
||||
xposedLog(it)
|
||||
context.log.error("Failed to download message", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.downloader
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Button
|
||||
import android.widget.RelativeLayout
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
@ -45,7 +44,7 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader", loadParams
|
||||
friendUsername!!
|
||||
)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to download profile picture", it)
|
||||
this@ProfilePictureDownloader.context.log.error("Failed to download profile picture", it)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.experiments
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
@ -17,11 +16,11 @@ class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParam
|
||||
val fingerprintClass = android.os.Build::class.java
|
||||
Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter ->
|
||||
hookAdapter.setResult(fingerprint)
|
||||
Logger.debug("Fingerprint spoofed to $fingerprint")
|
||||
context.log.verbose("Fingerprint spoofed to $fingerprint")
|
||||
}
|
||||
Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter ->
|
||||
hookAdapter.setResult(fingerprint)
|
||||
Logger.debug("Fingerprint spoofed to $fingerprint")
|
||||
context.log.verbose("Fingerprint spoofed to $fingerprint")
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +29,7 @@ class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParam
|
||||
Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter ->
|
||||
if(hookAdapter.args()[1] == "android_id") {
|
||||
hookAdapter.setResult(androidId)
|
||||
Logger.debug("Android ID spoofed to $androidId")
|
||||
context.log.verbose("Android ID spoofed to $androidId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.features.impl.privacy
|
||||
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||
import me.rhunk.snapenhance.data.NotificationType
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
@ -28,7 +27,7 @@ class PreventMessageSending : Feature("Prevent message sending", loadParams = Fe
|
||||
val associatedType = NotificationType.fromContentType(contentType) ?: return@subscribe
|
||||
|
||||
if (preventMessageSending.contains(associatedType.key)) {
|
||||
Logger.debug("Preventing message sending for $associatedType")
|
||||
context.log.verbose("Preventing message sending for $associatedType")
|
||||
event.canceled = true
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.spying
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.spying
|
||||
import android.os.DeadObjectException
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MessageState
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
@ -72,7 +71,7 @@ class MessageLogger : Feature("MessageLogger",
|
||||
context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
|
||||
fetchedMessages.addAll(context.bridgeClient.getLoggedMessageIds(friendFeedInfo.key!!, PREFETCH_MESSAGE_COUNT).toList())
|
||||
}
|
||||
}.also { Logger.debug("Loaded ${fetchedMessages.size} cached messages in $it") }
|
||||
}.also { context.log.verbose("Loaded ${fetchedMessages.size} cached messages in $it") }
|
||||
}
|
||||
|
||||
private fun processSnapMessage(messageInstance: Any) {
|
||||
|
@ -41,7 +41,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
||||
|
||||
val callback = CallbackBuilder(callbackClass)
|
||||
.override("onError") {
|
||||
Logger.xposedLog("Error saving message $messageId")
|
||||
context.log.warn("Error saving message $messageId")
|
||||
}.build()
|
||||
|
||||
runCatching {
|
||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.tweaks
|
||||
import android.os.Build
|
||||
import android.os.FileObserver
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
@ -32,7 +31,7 @@ class DisableVideoLengthRestriction : Feature("DisableVideoLengthRestriction", l
|
||||
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
|
||||
if (fileContent["timerOrDuration"].asLong < 0) file.delete()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to read story metadata file", it)
|
||||
context.log.error("Failed to read story metadata file", it)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import android.app.AlertDialog
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
@ -15,7 +14,7 @@ class GooglePlayServicesDialogs : Feature("Disable GMS Dialogs", loadParams = Fe
|
||||
findClass("com.google.android.gms.common.GoogleApiAvailability").methods
|
||||
.first { Modifier.isStatic(it.modifiers) && it.returnType == AlertDialog::class.java }.let { method ->
|
||||
method.hook(HookStage.BEFORE) { param ->
|
||||
Logger.debug("GoogleApiAvailability.showErrorDialogFragment() called, returning null")
|
||||
context.log.verbose("GoogleApiAvailability.showErrorDialogFragment() called, returning null")
|
||||
param.setResult(null)
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ import android.os.UserHandle
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
@ -297,7 +297,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
fetchMessagesResult(conversationId, messageList)
|
||||
}
|
||||
.override("onError") {
|
||||
Logger.xposedLog("Failed to fetch message ${it.arg(0) as Any}")
|
||||
context.log.error("Failed to fetch message ${it.arg(0) as Any}")
|
||||
}.build()
|
||||
|
||||
fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback)
|
||||
@ -323,7 +323,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
val intent = param.argNullable<Intent>(0) ?: return@hook
|
||||
val messageType = intent.getStringExtra("type") ?: return@hook
|
||||
|
||||
Logger.xposedLog("received message type: $messageType")
|
||||
context.log.debug("received message type: $messageType")
|
||||
|
||||
if (states.contains(messageType.replaceFirst("mischief_", ""))) {
|
||||
param.setResult(null)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.ui
|
||||
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.features.BridgeFileFeature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
|
@ -1,38 +1,37 @@
|
||||
package me.rhunk.snapenhance.manager.impl
|
||||
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.action.impl.CheckForUpdates
|
||||
import me.rhunk.snapenhance.action.impl.CleanCache
|
||||
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
|
||||
import me.rhunk.snapenhance.action.impl.ExportChatMessages
|
||||
import me.rhunk.snapenhance.action.impl.OpenMap
|
||||
import me.rhunk.snapenhance.action.impl.RefreshMappings
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.action.EnumAction
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class ActionManager(
|
||||
private val context: ModContext,
|
||||
private val modContext: ModContext,
|
||||
) : Manager {
|
||||
private val actions = mutableMapOf<String, AbstractAction>()
|
||||
fun getActions() = actions.values.toList()
|
||||
private fun load(clazz: KClass<out AbstractAction>) {
|
||||
val action = clazz.java.newInstance()
|
||||
action.context = context
|
||||
actions[action.nameKey] = action
|
||||
companion object {
|
||||
const val ACTION_PARAMETER = "se_action"
|
||||
}
|
||||
private val actions = mutableMapOf<String, AbstractAction>()
|
||||
|
||||
override fun init() {
|
||||
load(CleanCache::class)
|
||||
load(ExportChatMessages::class)
|
||||
load(OpenMap::class)
|
||||
load(CheckForUpdates::class)
|
||||
if(BuildConfig.DEBUG) {
|
||||
load(ClearMessageLogger::class)
|
||||
load(RefreshMappings::class)
|
||||
EnumAction.values().forEach { enumAction ->
|
||||
actions[enumAction.key] = enumAction.clazz.java.getConstructor().newInstance().apply {
|
||||
this.context = modContext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNewIntent(intent: Intent?) {
|
||||
val action = intent?.getStringExtra(ACTION_PARAMETER) ?: return
|
||||
execute(EnumAction.values().find { it.key == action } ?: return)
|
||||
intent.removeExtra(ACTION_PARAMETER)
|
||||
}
|
||||
|
||||
actions.values.forEach(AbstractAction::init)
|
||||
private fun execute(action: EnumAction) {
|
||||
actions[action.key]?.run()
|
||||
if (action.exitOnFinish) {
|
||||
modContext.forceCloseApp()
|
||||
}
|
||||
}
|
||||
}
|
@ -11,11 +11,10 @@ import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Switch
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.database.objects.ConversationMessage
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.database.objects.UserConversationLink
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.database.objects.ConversationMessage
|
||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.database.objects.UserConversationLink
|
||||
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||
@ -57,7 +56,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.xposedLog(e)
|
||||
context.log.error("Error loading bitmoji selfie", e)
|
||||
}
|
||||
val finalIcon = icon
|
||||
context.runOnUiThread {
|
||||
@ -243,7 +242,6 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
|
||||
rules.forEach { ruleFeature ->
|
||||
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
||||
Logger.debug("${ruleFeature.ruleType.key} ${ruleFeature.getRuleState()}")
|
||||
|
||||
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
||||
createToggleFeature(viewConsumer,
|
||||
|
@ -8,7 +8,6 @@ import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
@ -76,7 +75,7 @@ class OperaContextActionMenu : AbstractMenu() {
|
||||
linearLayout.addView(button)
|
||||
(childView as ViewGroup).addView(linearLayout, 0)
|
||||
} catch (e: Throwable) {
|
||||
Logger.xposedLog(e)
|
||||
context.log.error("Error while injecting OperaContextActionMenu", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,13 @@ package me.rhunk.snapenhance.ui.menu.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||
|
||||
class SettingsMenu : AbstractMenu() {
|
||||
//TODO: quick settings
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun inject(viewModel: View, addView: (View) -> Unit) {
|
||||
val actions = context.actionManager.getActions().map {
|
||||
/*val actions = context.actionManager.getActions().map {
|
||||
Pair(it) {
|
||||
val button = Button(viewModel.context)
|
||||
button.text = context.translation[it.nameKey]
|
||||
@ -25,6 +23,6 @@ class SettingsMenu : AbstractMenu() {
|
||||
|
||||
actions.forEach {
|
||||
addView(it.second())
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ object SQLiteDatabaseHelper {
|
||||
|
||||
if (newColumns.isEmpty()) return@forEach
|
||||
|
||||
Logger.log("Schema for table $tableName has changed")
|
||||
Logger.directDebug("Schema for table $tableName has changed")
|
||||
sqLiteDatabase.execSQL("DROP TABLE $tableName")
|
||||
sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS $tableName (${columns.joinToString(", ")})")
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class HttpServer(
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
Logger.debug("starting http server on port $port")
|
||||
Logger.directDebug("starting http server on port $port")
|
||||
serverSocket = ServerSocket(port)
|
||||
callback(this@HttpServer)
|
||||
while (!serverSocket!!.isClosed) {
|
||||
@ -48,21 +48,21 @@ class HttpServer(
|
||||
handleRequest(socket)
|
||||
timeoutJob = launch {
|
||||
delay(timeout.toLong())
|
||||
Logger.debug("http server closed due to timeout")
|
||||
Logger.directDebug("http server closed due to timeout")
|
||||
runCatching {
|
||||
socketJob?.cancel()
|
||||
socket.close()
|
||||
serverSocket?.close()
|
||||
}.onFailure {
|
||||
Logger.error(it)
|
||||
Logger.directError("failed to close socket", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SocketException) {
|
||||
Logger.debug("http server timed out")
|
||||
Logger.directDebug("http server timed out")
|
||||
break;
|
||||
} catch (e: Throwable) {
|
||||
Logger.error("failed to handle request", e)
|
||||
Logger.directError("failed to handle request", e)
|
||||
}
|
||||
}
|
||||
}.also { socketJob = it }
|
||||
@ -90,13 +90,13 @@ class HttpServer(
|
||||
outputStream.close()
|
||||
socket.close()
|
||||
}.onFailure {
|
||||
Logger.error("failed to close socket", it)
|
||||
Logger.directError("failed to close socket", it)
|
||||
}
|
||||
}
|
||||
val parse = StringTokenizer(line)
|
||||
val method = parse.nextToken().uppercase(Locale.getDefault())
|
||||
var fileRequested = parse.nextToken().lowercase(Locale.getDefault())
|
||||
Logger.debug("[http-server:${port}] $method $fileRequested")
|
||||
Logger.directDebug("[http-server:${port}] $method $fileRequested")
|
||||
|
||||
if (method != "GET") {
|
||||
with(writer) {
|
||||
|
@ -44,7 +44,7 @@ object RemoteMediaResolver {
|
||||
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
Logger.log("Unexpected code $response")
|
||||
Logger.directDebug("Unexpected code $response")
|
||||
return null
|
||||
}
|
||||
return ByteArrayInputStream(response.body.bytes())
|
||||
|
@ -9,16 +9,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||
@ -138,7 +137,7 @@ class MessageExporter(
|
||||
}
|
||||
}.onFailure {
|
||||
printLog("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}")
|
||||
Logger.error("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}", it)
|
||||
context.log.error("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -219,7 +218,7 @@ class MessageExporter(
|
||||
}
|
||||
}.onFailure {
|
||||
printLog("failed to read template from apk")
|
||||
Logger.error("failed to read template from apk", it)
|
||||
context.log.error("failed to read template from apk", it)
|
||||
}
|
||||
|
||||
output.write("</html>".toByteArray())
|
||||
|
@ -4,9 +4,9 @@ import com.arthenica.ffmpegkit.FFmpegKit
|
||||
import com.arthenica.ffmpegkit.FFmpegSession
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import java.io.ByteArrayInputStream
|
||||
|
Loading…
x
Reference in New Issue
Block a user