mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 21:10:20 +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.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.core.app.CoreComponentFactory
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.decode.VideoFrameDecoder
|
import coil.decode.VideoFrameDecoder
|
||||||
@ -13,18 +16,25 @@ import coil.disk.DiskCache
|
|||||||
import coil.memory.MemoryCache
|
import coil.memory.MemoryCache
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import me.rhunk.snapenhance.bridge.BridgeService
|
import me.rhunk.snapenhance.bridge.BridgeService
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
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.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.ModDatabase
|
||||||
import me.rhunk.snapenhance.messaging.StreaksReminder
|
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.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.manager.data.SnapchatAppInfo
|
||||||
import me.rhunk.snapenhance.ui.setup.Requirements
|
import me.rhunk.snapenhance.ui.setup.Requirements
|
||||||
import me.rhunk.snapenhance.ui.setup.SetupActivity
|
import me.rhunk.snapenhance.ui.setup.SetupActivity
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
|
|
||||||
class RemoteSideContext(
|
class RemoteSideContext(
|
||||||
val androidContext: Context
|
val androidContext: Context
|
||||||
@ -42,6 +52,7 @@ class RemoteSideContext(
|
|||||||
val downloadTaskManager = DownloadTaskManager()
|
val downloadTaskManager = DownloadTaskManager()
|
||||||
val modDatabase = ModDatabase(this)
|
val modDatabase = ModDatabase(this)
|
||||||
val streaksReminder = StreaksReminder(this)
|
val streaksReminder = StreaksReminder(this)
|
||||||
|
val log = LogManager(this)
|
||||||
|
|
||||||
//used to load bitmoji selfies and download previews
|
//used to load bitmoji selfies and download previews
|
||||||
val imageLoader by lazy {
|
val imageLoader by lazy {
|
||||||
@ -76,37 +87,57 @@ class RemoteSideContext(
|
|||||||
modDatabase.init()
|
modDatabase.init()
|
||||||
streaksReminder.init()
|
streaksReminder.init()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to load RemoteSideContext", it)
|
log.error("Failed to load RemoteSideContext", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstallationSummary() = InstallationSummary(
|
val installationSummary by lazy {
|
||||||
snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
|
InstallationSummary(
|
||||||
SnapchatAppInfo(
|
snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
|
||||||
version = it.versionName,
|
SnapchatAppInfo(
|
||||||
versionCode = it.longVersionCode
|
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) {
|
fun longToast(message: Any) {
|
||||||
androidContext.mainExecutor.execute {
|
androidContext.mainExecutor.execute {
|
||||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
Logger.debug(message.toString())
|
log.debug(message.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shortToast(message: Any) {
|
fun shortToast(message: Any) {
|
||||||
androidContext.mainExecutor.execute {
|
androidContext.mainExecutor.execute {
|
||||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
Logger.debug(message.toString())
|
log.debug(message.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
|
fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
|
||||||
|
@ -3,16 +3,16 @@ package me.rhunk.snapenhance.bridge
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.LogLevel
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
import me.rhunk.snapenhance.SharedContextHolder
|
import me.rhunk.snapenhance.SharedContextHolder
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
import me.rhunk.snapenhance.core.bridge.types.FileActionType
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.MessageLoggerWrapper
|
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.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
|
||||||
import me.rhunk.snapenhance.download.DownloadProcessor
|
import me.rhunk.snapenhance.download.DownloadProcessor
|
||||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
@ -36,7 +36,7 @@ class BridgeService : Service() {
|
|||||||
fun triggerFriendSync(friendId: String) {
|
fun triggerFriendSync(friendId: String) {
|
||||||
val syncedFriend = syncCallback.syncFriend(friendId)
|
val syncedFriend = syncCallback.syncFriend(friendId)
|
||||||
if (syncedFriend == null) {
|
if (syncedFriend == null) {
|
||||||
Logger.error("Failed to sync friend $friendId")
|
remoteSideContext.log.error("Failed to sync friend $friendId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
|
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
|
||||||
@ -47,7 +47,7 @@ class BridgeService : Service() {
|
|||||||
fun triggerGroupSync(groupId: String) {
|
fun triggerGroupSync(groupId: String) {
|
||||||
val syncedGroup = syncCallback.syncGroup(groupId)
|
val syncedGroup = syncCallback.syncGroup(groupId)
|
||||||
if (syncedGroup == null) {
|
if (syncedGroup == null) {
|
||||||
Logger.error("Failed to sync group $groupId")
|
remoteSideContext.log.error("Failed to sync group $groupId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
|
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
|
||||||
@ -56,10 +56,12 @@ class BridgeService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner class BridgeBinder : BridgeInterface.Stub() {
|
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 {
|
override fun fileOperation(action: Int, fileType: Int, content: ByteArray?): ByteArray {
|
||||||
val resolvedFile by lazy {
|
val resolvedFile = BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
|
||||||
BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (FileActionType.values()[action]) {
|
return when (FileActionType.values()[action]) {
|
||||||
FileActionType.CREATE_AND_READ -> {
|
FileActionType.CREATE_AND_READ -> {
|
||||||
@ -108,8 +110,6 @@ class BridgeService : Service() {
|
|||||||
override fun deleteMessageLoggerMessage(conversationId: String, id: Long) =
|
override fun deleteMessageLoggerMessage(conversationId: String, id: Long) =
|
||||||
messageLoggerWrapper.deleteMessage(conversationId, id)
|
messageLoggerWrapper.deleteMessage(conversationId, id)
|
||||||
|
|
||||||
override fun clearMessageLogger() = messageLoggerWrapper.clearMessages()
|
|
||||||
|
|
||||||
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
|
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
|
||||||
|
|
||||||
override fun fetchLocales(userLocale: String) =
|
override fun fetchLocales(userLocale: String) =
|
||||||
@ -133,25 +133,24 @@ class BridgeService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun sync(callback: SyncCallback) {
|
override fun sync(callback: SyncCallback) {
|
||||||
Logger.debug("Syncing remote")
|
|
||||||
syncCallback = callback
|
syncCallback = callback
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
|
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
|
||||||
runCatching {
|
runCatching {
|
||||||
triggerFriendSync(friendId)
|
triggerFriendSync(friendId)
|
||||||
}.onFailure {
|
}.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 ->
|
remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId ->
|
||||||
runCatching {
|
runCatching {
|
||||||
triggerGroupSync(groupId)
|
triggerGroupSync(groupId)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to sync group $groupId", it)
|
remoteSideContext.log.error("Failed to sync group $groupId", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
}.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>,
|
groups: List<String>,
|
||||||
friends: 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(
|
remoteSideContext.modDatabase.receiveMessagingDataCallback(
|
||||||
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) },
|
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) },
|
||||||
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(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.Logger
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||||
|
import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.download.data.DownloadRequest
|
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
||||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||||
import me.rhunk.snapenhance.download.data.InputMedia
|
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||||
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
|
import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
|
||||||
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
||||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -178,14 +179,14 @@ class DownloadProcessor (
|
|||||||
mediaScanIntent.setData(outputFile.uri)
|
mediaScanIntent.setData(outputFile.uri)
|
||||||
remoteSideContext.androidContext.sendBroadcast(mediaScanIntent)
|
remoteSideContext.androidContext.sendBroadcast(mediaScanIntent)
|
||||||
}.onFailure {
|
}.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)
|
callbackOnFailure(translation.format("failed_gallery_toast", "error" to it.toString()), it.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.debug("download complete")
|
remoteSideContext.log.verbose("download complete")
|
||||||
callbackOnSuccess(fileName)
|
callbackOnSuccess(fileName)
|
||||||
}.onFailure { exception ->
|
}.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)
|
callbackOnFailure(translation.format("failed_gallery_toast", "error" to exception.toString()), exception.message)
|
||||||
downloadObject.downloadStage = DownloadStage.FAILED
|
downloadObject.downloadStage = DownloadStage.FAILED
|
||||||
}
|
}
|
||||||
@ -284,7 +285,7 @@ class DownloadProcessor (
|
|||||||
saveMediaToGallery(outputFile, downloadObjectObject)
|
saveMediaToGallery(outputFile, downloadObjectObject)
|
||||||
}.onFailure { exception ->
|
}.onFailure { exception ->
|
||||||
if (coroutineContext.job.isCancelled) return@onFailure
|
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)
|
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
|
||||||
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
||||||
}
|
}
|
||||||
@ -333,7 +334,7 @@ class DownloadProcessor (
|
|||||||
val downloadedMedias = downloadInputMedias(downloadRequest).map {
|
val downloadedMedias = downloadInputMedias(downloadRequest).map {
|
||||||
it.key to DownloadedFile(it.value, FileType.fromFile(it.value))
|
it.key to DownloadedFile(it.value, FileType.fromFile(it.value))
|
||||||
}.toMap().toMutableMap()
|
}.toMap().toMutableMap()
|
||||||
Logger.debug("downloaded ${downloadedMedias.size} medias")
|
remoteSideContext.log.verbose("downloaded ${downloadedMedias.size} medias")
|
||||||
|
|
||||||
var shouldMergeOverlay = downloadRequest.shouldMergeOverlay
|
var shouldMergeOverlay = downloadRequest.shouldMergeOverlay
|
||||||
|
|
||||||
@ -376,7 +377,7 @@ class DownloadProcessor (
|
|||||||
saveMediaToGallery(mergedOverlay, downloadObjectObject)
|
saveMediaToGallery(mergedOverlay, downloadObjectObject)
|
||||||
}.onFailure { exception ->
|
}.onFailure { exception ->
|
||||||
if (coroutineContext.job.isCancelled) return@onFailure
|
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)
|
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
|
||||||
downloadObjectObject.downloadStage = DownloadStage.MERGE_FAILED
|
downloadObjectObject.downloadStage = DownloadStage.MERGE_FAILED
|
||||||
}
|
}
|
||||||
@ -390,7 +391,7 @@ class DownloadProcessor (
|
|||||||
downloadRemoteMedia(downloadObjectObject, downloadedMedias, downloadRequest)
|
downloadRemoteMedia(downloadObjectObject, downloadedMedias, downloadRequest)
|
||||||
}.onFailure { exception ->
|
}.onFailure { exception ->
|
||||||
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
downloadObjectObject.downloadStage = DownloadStage.FAILED
|
||||||
Logger.error(exception)
|
remoteSideContext.log.error("Failed to download media", exception)
|
||||||
callbackOnFailure(translation["failed_generic_toast"], exception.message)
|
callbackOnFailure(translation["failed_generic_toast"], exception.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package me.rhunk.snapenhance.messaging
|
package me.rhunk.snapenhance.messaging
|
||||||
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
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.FriendStreaks
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
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.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getLongOrNull
|
import me.rhunk.snapenhance.util.ktx.getLongOrNull
|
||||||
@ -28,7 +27,7 @@ class ModDatabase(
|
|||||||
runCatching {
|
runCatching {
|
||||||
block()
|
block()
|
||||||
}.onFailure {
|
}.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")
|
selfieId = cursor.getStringOrNull("selfieId")
|
||||||
))
|
))
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to parse friend", it)
|
context.log.error("Failed to parse friend", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
friends
|
friends
|
||||||
|
@ -13,7 +13,7 @@ import androidx.navigation.NavController
|
|||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
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.NotImplemented
|
||||||
import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection
|
import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection
|
||||||
import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection
|
import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection
|
||||||
@ -64,6 +64,8 @@ open class Section {
|
|||||||
lateinit var context: RemoteSideContext
|
lateinit var context: RemoteSideContext
|
||||||
lateinit var navController: NavController
|
lateinit var navController: NavController
|
||||||
|
|
||||||
|
val currentRoute get() = navController.currentBackStackEntry?.destination?.route
|
||||||
|
|
||||||
open fun init() {}
|
open fun init() {}
|
||||||
open fun onResumed() {}
|
open fun onResumed() {}
|
||||||
|
|
||||||
|
@ -2,16 +2,33 @@ package me.rhunk.snapenhance.ui.manager.data
|
|||||||
|
|
||||||
|
|
||||||
data class SnapchatAppInfo(
|
data class SnapchatAppInfo(
|
||||||
|
val packageName: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val versionCode: Long
|
val versionCode: Long,
|
||||||
|
val isLSPatched: Boolean,
|
||||||
|
val isSplitApk: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ModMappingsInfo(
|
data class ModInfo(
|
||||||
val generatedSnapchatVersion: Long,
|
val loaderPackageName: String,
|
||||||
val isOutdated: Boolean
|
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(
|
data class InstallationSummary(
|
||||||
|
val platformInfo: PlatformInfo,
|
||||||
val snapchatInfo: SnapchatAppInfo?,
|
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 coil.compose.rememberAsyncImagePainter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
||||||
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
||||||
|
@ -439,7 +439,7 @@ class FeaturesSection : Section() {
|
|||||||
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
showSearchBar = showSearchBar.not()
|
showSearchBar = showSearchBar.not()
|
||||||
if (!showSearchBar && navController.currentBackStackEntry?.destination?.route == SEARCH_FEATURE_ROUTE) {
|
if (!showSearchBar && currentRoute == SEARCH_FEATURE_ROUTE) {
|
||||||
navController.navigate(MAIN_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.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
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.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||||
@ -55,8 +54,8 @@ class AddFriendDialog(
|
|||||||
private val section: SocialSection,
|
private val section: SocialSection,
|
||||||
) {
|
) {
|
||||||
@Composable
|
@Composable
|
||||||
private fun ListCardEntry(name: String, currentState: () -> Boolean, onState: (Boolean) -> Unit = {}) {
|
private fun ListCardEntry(name: String, getCurrentState: () -> Boolean, onState: (Boolean) -> Unit = {}) {
|
||||||
var currentState by remember { mutableStateOf(currentState()) }
|
var currentState by remember { mutableStateOf(getCurrentState()) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -74,7 +73,7 @@ class AddFriendDialog(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
currentState = currentState()
|
currentState = getCurrentState()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -149,7 +148,7 @@ class AddFriendDialog(
|
|||||||
runCatching {
|
runCatching {
|
||||||
context.androidContext.sendBroadcast(it)
|
context.androidContext.sendBroadcast(it)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to send broadcast", it)
|
context.log.error("Failed to send broadcast", it)
|
||||||
hasFetchError = true
|
hasFetchError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,7 +233,7 @@ class AddFriendDialog(
|
|||||||
val group = filteredGroups[it]
|
val group = filteredGroups[it]
|
||||||
ListCardEntry(
|
ListCardEntry(
|
||||||
name = group.name,
|
name = group.name,
|
||||||
currentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
|
getCurrentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
|
||||||
) { state ->
|
) { state ->
|
||||||
if (state) {
|
if (state) {
|
||||||
context.bridgeService.triggerGroupSync(group.conversationId)
|
context.bridgeService.triggerGroupSync(group.conversationId)
|
||||||
@ -261,7 +260,7 @@ class AddFriendDialog(
|
|||||||
|
|
||||||
ListCardEntry(
|
ListCardEntry(
|
||||||
name = friend.displayName?.takeIf { name -> name.isNotBlank() } ?: friend.mutableUsername,
|
name = friend.displayName?.takeIf { name -> name.isNotBlank() } ?: friend.mutableUsername,
|
||||||
currentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
|
getCurrentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
|
||||||
) { state ->
|
) { state ->
|
||||||
if (state) {
|
if (state) {
|
||||||
context.bridgeService.triggerFriendSync(friend.userId)
|
context.bridgeService.triggerFriendSync(friend.userId)
|
||||||
|
@ -79,7 +79,7 @@ class SocialSection : Section() {
|
|||||||
groupList = context.modDatabase.getGroups()
|
groupList = context.modDatabase.getGroups()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canGoBack() = navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE
|
override fun canGoBack() = currentRoute != MAIN_ROUTE
|
||||||
|
|
||||||
override fun build(navGraphBuilder: NavGraphBuilder) {
|
override fun build(navGraphBuilder: NavGraphBuilder) {
|
||||||
navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) {
|
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(
|
IconButton(
|
||||||
onClick = { deleteConfirmDialog = true },
|
onClick = { deleteConfirmDialog = true },
|
||||||
) {
|
) {
|
||||||
|
@ -44,7 +44,7 @@ class MappingsScreen : SetupScreen() {
|
|||||||
|
|
||||||
fun tryToGenerateMappings() {
|
fun tryToGenerateMappings() {
|
||||||
//check for snapchat installation
|
//check for snapchat installation
|
||||||
val installationSummary = context.getInstallationSummary()
|
val installationSummary = context.installationSummary
|
||||||
if (installationSummary.snapchatInfo == null) {
|
if (installationSummary.snapchatInfo == null) {
|
||||||
throw Exception(context.translation["setup.mappings.generate_failure_no_snapchat"])
|
throw Exception(context.translation["setup.mappings.generate_failure_no_snapchat"])
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ class MappingsScreen : SetupScreen() {
|
|||||||
}.onFailure {
|
}.onFailure {
|
||||||
isGenerating = false
|
isGenerating = false
|
||||||
infoText = context.translation["setup.mappings.generate_failure"] + "\n\n" + it.message
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
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.setup.screens.SetupScreen
|
||||||
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -7,7 +7,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||||
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
import me.rhunk.snapenhance.ui.util.ObservableMutableState
|
||||||
@ -23,7 +22,6 @@ class SaveFolderScreen : SetupScreen() {
|
|||||||
saveFolder = ObservableMutableState(
|
saveFolder = ObservableMutableState(
|
||||||
defaultValue = "",
|
defaultValue = "",
|
||||||
onChange = { _, newValue ->
|
onChange = { _, newValue ->
|
||||||
Logger.debug(newValue)
|
|
||||||
if (newValue.isNotBlank()) {
|
if (newValue.isNotBlank()) {
|
||||||
context.config.root.downloader.saveFolder.set(newValue)
|
context.config.root.downloader.saveFolder.set(newValue)
|
||||||
context.config.writeConfig()
|
context.config.writeConfig()
|
||||||
|
@ -16,7 +16,7 @@ class ActivityLauncherHelper(
|
|||||||
runCatching {
|
runCatching {
|
||||||
callback?.let { it(result.data!!) }
|
callback?.let { it(result.data!!) }
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to process activity result", it)
|
Logger.directError("Failed to process activity result", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback = null
|
callback = null
|
||||||
|
@ -33,7 +33,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.DataProcessors
|
||||||
import me.rhunk.snapenhance.core.config.PropertyPair
|
import me.rhunk.snapenhance.core.config.PropertyPair
|
||||||
|
|
||||||
|
@ -5,53 +5,47 @@ import me.rhunk.snapenhance.bridge.DownloadCallback;
|
|||||||
import me.rhunk.snapenhance.bridge.SyncCallback;
|
import me.rhunk.snapenhance.bridge.SyncCallback;
|
||||||
|
|
||||||
interface BridgeInterface {
|
interface BridgeInterface {
|
||||||
|
/**
|
||||||
|
* broadcast a log message
|
||||||
|
*/
|
||||||
|
void broadcastLog(String tag, String level, String message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a file operation
|
* Execute a file operation
|
||||||
|
* @param fileType the corresponding file type (see BridgeFileType)
|
||||||
*/
|
*/
|
||||||
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the content of a logged message from the database
|
* Get the content of a logged message from the database
|
||||||
*
|
* @return message ids that are logged
|
||||||
* @param conversationId the ID of the conversation
|
|
||||||
* @return the content of the message
|
|
||||||
*/
|
*/
|
||||||
long[] getLoggedMessageIds(String conversationId, int limit);
|
long[] getLoggedMessageIds(String conversationId, int limit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the content of a logged message from the database
|
* 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);
|
@nullable byte[] getMessageLoggerMessage(String conversationId, long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a message to the message logger database
|
* 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);
|
void addMessageLoggerMessage(String conversationId, long id, in byte[] message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a message from the message logger database
|
* Delete a message from the message logger database
|
||||||
*
|
|
||||||
* @param id the ID of the message logger message
|
|
||||||
*/
|
*/
|
||||||
void deleteMessageLoggerMessage(String conversationId, long id);
|
void deleteMessageLoggerMessage(String conversationId, long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the message logger database
|
* Get the application APK path (assets for the conversation exporter)
|
||||||
*/
|
*/
|
||||||
void clearMessageLogger();
|
|
||||||
|
|
||||||
String getApplicationApkPath();
|
String getApplicationApkPath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the locales
|
* 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);
|
Map<String, String> fetchLocales(String userLocale);
|
||||||
|
|
||||||
@ -62,11 +56,14 @@ interface BridgeInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get rules for a given user or conversation
|
* Get rules for a given user or conversation
|
||||||
|
* @return list of rules (MessagingRuleType)
|
||||||
*/
|
*/
|
||||||
List<String> getRules(String uuid);
|
List<String> getRules(String uuid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update rule for a giver user or conversation
|
* Update rule for a giver user or conversation
|
||||||
|
*
|
||||||
|
* @param type rule type (MessagingRuleType)
|
||||||
*/
|
*/
|
||||||
void setRule(String uuid, String type, boolean state);
|
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
|
* Pass all groups and friends to be able to add them to the database
|
||||||
* @param groups serialized groups
|
* @param groups list of groups (MessagingGroupInfo as json string)
|
||||||
* @param friends serialized friends
|
* @param friends list of friends (MessagingFriendInfo as json string)
|
||||||
*/
|
*/
|
||||||
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
|
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
|
||||||
}
|
}
|
@ -20,6 +20,8 @@
|
|||||||
"downloads": "Downloads",
|
"downloads": "Downloads",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"home_debug": "Debug",
|
||||||
|
"home_logs": "Logs",
|
||||||
"social": "Social",
|
"social": "Social",
|
||||||
"plugins": "Plugins"
|
"plugins": "Plugins"
|
||||||
},
|
},
|
||||||
@ -64,8 +66,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"action": {
|
"actions": {
|
||||||
"clean_cache": "Clean Cache",
|
"clean_snapchat_cache": "Clean Snapchat Cache",
|
||||||
"clear_message_logger": "Clear Message Logger",
|
"clear_message_logger": "Clear Message Logger",
|
||||||
"refresh_mappings": "Refresh Mappings",
|
"refresh_mappings": "Refresh Mappings",
|
||||||
"open_map": "Choose location on map",
|
"open_map": "Choose location on map",
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package me.rhunk.snapenhance
|
package me.rhunk.snapenhance
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
const val TAG = "SnapEnhance"
|
|
||||||
const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android"
|
const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android"
|
||||||
|
|
||||||
const val VIEW_INJECTED_CODE = 0x7FFFFF02
|
const val VIEW_INJECTED_CODE = 0x7FFFFF02
|
||||||
|
|
||||||
val ARROYO_MEDIA_CONTAINER_PROTO_PATH = intArrayOf(4, 4)
|
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_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 = 19
|
||||||
const val ENCRYPTION_PROTO_INDEX_V2 = 4
|
const val ENCRYPTION_PROTO_INDEX_V2 = 4
|
||||||
|
@ -2,47 +2,81 @@ package me.rhunk.snapenhance
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||||
|
|
||||||
object Logger {
|
enum class LogLevel(
|
||||||
private const val TAG = "SnapEnhance"
|
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?) {
|
companion object {
|
||||||
Log.i(TAG, message.toString())
|
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?) {
|
private fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
|
||||||
if (!BuildConfig.DEBUG) return
|
runCatching {
|
||||||
Log.d(TAG, message.toString())
|
bridgeClient.broadcastLog(tag, logLevel.shortName, message.toString())
|
||||||
|
}.onFailure {
|
||||||
|
Log.println(logLevel.priority, tag, message.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun debug(tag: String, message: Any?) {
|
fun debug(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.DEBUG, message)
|
||||||
if (!BuildConfig.DEBUG) return
|
|
||||||
Log.d(tag, message.toString())
|
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) {
|
fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message)
|
||||||
Log.e(TAG, "", throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(message: Any?) {
|
fun verbose(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.VERBOSE, message)
|
||||||
Log.e(TAG, message.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(message: Any?, throwable: Throwable) {
|
fun warn(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.WARN, message)
|
||||||
Log.e(TAG, message.toString(), throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun xposedLog(message: Any?) {
|
fun assert(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ASSERT, message)
|
||||||
XposedBridge.log(message.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun xposedLog(message: Any?, throwable: Throwable?) {
|
|
||||||
XposedBridge.log(message.toString())
|
|
||||||
XposedBridge.log(throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun xposedLog(throwable: Throwable) {
|
|
||||||
XposedBridge.log(throwable)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -11,13 +11,13 @@ import android.widget.Toast
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
|
||||||
import me.rhunk.snapenhance.core.config.ModConfig
|
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.core.eventbus.EventBus
|
||||||
import me.rhunk.snapenhance.data.MessageSender
|
import me.rhunk.snapenhance.data.MessageSender
|
||||||
import me.rhunk.snapenhance.database.DatabaseAccess
|
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.manager.impl.ActionManager
|
import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||||
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
||||||
@ -44,6 +44,7 @@ class ModContext {
|
|||||||
|
|
||||||
private val modConfig = ModConfig()
|
private val modConfig = ModConfig()
|
||||||
val config by modConfig
|
val config by modConfig
|
||||||
|
val log by lazy { Logger(this.bridgeClient) }
|
||||||
val event = EventBus(this)
|
val event = EventBus(this)
|
||||||
val eventDispatcher = EventDispatcher(this)
|
val eventDispatcher = EventDispatcher(this)
|
||||||
val native = NativeLib()
|
val native = NativeLib()
|
||||||
@ -81,13 +82,13 @@ class ModContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shortToast(message: Any) {
|
fun shortToast(message: Any?) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun longToast(message: Any) {
|
fun longToast(message: Any?) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
@ -108,7 +109,7 @@ class ModContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun crash(message: String, throwable: Throwable? = null) {
|
fun crash(message: String, throwable: Throwable? = null) {
|
||||||
Logger.xposedLog(message, throwable)
|
Logger.xposedLog(message, throwable ?: Exception())
|
||||||
longToast(message)
|
longToast(message)
|
||||||
delayForceCloseApp(100)
|
delayForceCloseApp(100)
|
||||||
}
|
}
|
||||||
@ -123,6 +124,7 @@ class ModContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reloadConfig() {
|
fun reloadConfig() {
|
||||||
|
log.verbose("reloading config")
|
||||||
modConfig.loadFromBridge(bridgeClient)
|
modConfig.loadFromBridge(bridgeClient)
|
||||||
native.loadNativeConfig(
|
native.loadNativeConfig(
|
||||||
NativeConfig(
|
NativeConfig(
|
||||||
|
@ -6,9 +6,9 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
|
||||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
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.SnapWidgetBroadcastReceiveEvent
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
|
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
@ -88,7 +88,7 @@ class SnapEnhance {
|
|||||||
return@hook
|
return@hook
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.debug("Reloading config")
|
appContext.actionManager.onNewIntent(activity.intent)
|
||||||
appContext.reloadConfig()
|
appContext.reloadConfig()
|
||||||
syncRemote()
|
syncRemote()
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ class SnapEnhance {
|
|||||||
syncRemote()
|
syncRemote()
|
||||||
}
|
}
|
||||||
}.also { time ->
|
}.also { time ->
|
||||||
Logger.debug("init took $time")
|
appContext.log.verbose("init took $time")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class SnapEnhance {
|
|||||||
actionManager.init()
|
actionManager.init()
|
||||||
}
|
}
|
||||||
}.also { time ->
|
}.also { time ->
|
||||||
Logger.debug("onActivityCreate took $time")
|
appContext.log.verbose("onActivityCreate took $time")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,6 @@ class SnapEnhance {
|
|||||||
val database = appContext.database
|
val database = appContext.database
|
||||||
|
|
||||||
appContext.executeAsync {
|
appContext.executeAsync {
|
||||||
Logger.debug("request remote sync")
|
|
||||||
appContext.bridgeClient.sync(object : SyncCallback.Stub() {
|
appContext.bridgeClient.sync(object : SyncCallback.Stub() {
|
||||||
override fun syncFriend(uuid: String): String? {
|
override fun syncFriend(uuid: String): String? {
|
||||||
return database.getFriendInfo(uuid)?.toJson()
|
return database.getFriendInfo(uuid)?.toJson()
|
||||||
|
@ -3,16 +3,9 @@ package me.rhunk.snapenhance.action
|
|||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
abstract class AbstractAction(
|
abstract class AbstractAction{
|
||||||
val nameKey: String
|
|
||||||
) {
|
|
||||||
lateinit var context: ModContext
|
lateinit var context: ModContext
|
||||||
|
|
||||||
/**
|
|
||||||
* called on the main thread when the mod initialize
|
|
||||||
*/
|
|
||||||
open fun init() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called when the action is triggered
|
* 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 me.rhunk.snapenhance.action.AbstractAction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class CleanCache : AbstractAction("action.clean_cache") {
|
class CleanCache : AbstractAction() {
|
||||||
companion object {
|
companion object {
|
||||||
private val FILES = arrayOf(
|
private val FILES = arrayOf(
|
||||||
"files/mbgl-offline.db",
|
"files/mbgl-offline.db",
|
||||||
@ -22,7 +22,7 @@ class CleanCache : AbstractAction("action.clean_cache") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
FILES.forEach {fileName ->
|
FILES.forEach { fileName ->
|
||||||
val fileCache = File(context.androidContext.dataDir, fileName)
|
val fileCache = File(context.androidContext.dataDir, fileName)
|
||||||
if (fileName.endsWith("*")) {
|
if (fileName.endsWith("*")) {
|
||||||
val parent = fileCache.parentFile ?: throw IllegalStateException("Parent file is null")
|
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 kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
import me.rhunk.snapenhance.action.AbstractAction
|
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.ContentType
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
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.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||||
import me.rhunk.snapenhance.util.CallbackBuilder
|
import me.rhunk.snapenhance.util.CallbackBuilder
|
||||||
@ -26,7 +26,7 @@ import me.rhunk.snapenhance.util.export.MessageExporter
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@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 callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
||||||
|
|
||||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
||||||
@ -55,7 +55,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
|||||||
context.runOnUiThread {
|
context.runOnUiThread {
|
||||||
if (dialogLogs.size > 15) dialogLogs.removeAt(0)
|
if (dialogLogs.size > 15) dialogLogs.removeAt(0)
|
||||||
dialogLogs.add(message)
|
dialogLogs.add(message)
|
||||||
Logger.debug("dialog: $message")
|
context.log.debug("dialog: $message")
|
||||||
currentActionDialog!!.setMessage(dialogLogs.joinToString("\n"))
|
currentActionDialog!!.setMessage(dialogLogs.joinToString("\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,7 +198,6 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Logger.debug("[$conversationName] fetching $lastMessageId")
|
|
||||||
val messages = fetchMessagesPaginated(conversationId, lastMessageId)
|
val messages = fetchMessagesPaginated(conversationId, lastMessageId)
|
||||||
if (messages.isEmpty()) break
|
if (messages.isEmpty()) break
|
||||||
foundMessages.addAll(messages)
|
foundMessages.addAll(messages)
|
||||||
@ -224,7 +223,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
|||||||
it.readMessages(foundMessages)
|
it.readMessages(foundMessages)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logDialog(context.translation.format("chat_export.export_failed","conversation" to it.message.toString()))
|
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
|
return
|
||||||
}
|
}
|
||||||
}.exportTo(exportType!!)
|
}.exportTo(exportType!!)
|
||||||
|
@ -5,7 +5,7 @@ import android.os.Bundle
|
|||||||
import me.rhunk.snapenhance.action.AbstractAction
|
import me.rhunk.snapenhance.action.AbstractAction
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
|
|
||||||
class OpenMap: AbstractAction("action.open_map") {
|
class OpenMap: AbstractAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
context.runOnUiThread {
|
context.runOnUiThread {
|
||||||
val mapActivityIntent = Intent()
|
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
|
import android.content.ComponentName
|
||||||
@ -10,11 +10,13 @@ import android.os.Handler
|
|||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import me.rhunk.snapenhance.Logger.xposedLog
|
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.bridge.BridgeInterface
|
||||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||||
|
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
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.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.data.LocalePair
|
import me.rhunk.snapenhance.data.LocalePair
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
@ -29,7 +31,7 @@ class BridgeClient(
|
|||||||
private lateinit var service: BridgeInterface
|
private lateinit var service: BridgeInterface
|
||||||
|
|
||||||
companion object {
|
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) {
|
fun start(callback: (Boolean) -> Unit) {
|
||||||
@ -76,7 +78,7 @@ class BridgeClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNullBinding(name: ComponentName) {
|
override fun onNullBinding(name: ComponentName) {
|
||||||
xposedLog("failed to connect to bridge service")
|
context.log.error("BridgeClient", "failed to connect to bridge service")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +86,8 @@ class BridgeClient(
|
|||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun broadcastLog(tag: String, level: String, message: String) = service.broadcastLog(tag, level, message)
|
||||||
|
|
||||||
fun createAndReadFile(
|
fun createAndReadFile(
|
||||||
fileType: BridgeFileType,
|
fileType: BridgeFileType,
|
||||||
defaultContent: ByteArray
|
defaultContent: ByteArray
|
||||||
@ -108,8 +112,6 @@ class BridgeClient(
|
|||||||
|
|
||||||
fun deleteMessageLoggerMessage(conversationId: String, id: Long) = service.deleteMessageLoggerMessage(conversationId, id)
|
fun deleteMessageLoggerMessage(conversationId: String, id: Long) = service.deleteMessageLoggerMessage(conversationId, id)
|
||||||
|
|
||||||
fun clearMessageLogger() = service.clearMessageLogger()
|
|
||||||
|
|
||||||
fun fetchLocales(userLocale: String) = service.fetchLocales(userLocale).map {
|
fun fetchLocales(userLocale: String) = service.fetchLocales(userLocale).map {
|
||||||
LocalePair(it.key, it.value)
|
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 android.content.Context
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||||
|
|
||||||
open class FileLoaderWrapper(
|
open class FileLoaderWrapper(
|
||||||
private val fileType: BridgeFileType,
|
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 android.content.Context
|
||||||
import java.io.File
|
import java.io.File
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.bridge.types
|
package me.rhunk.snapenhance.core.bridge.types
|
||||||
|
|
||||||
enum class FileActionType {
|
enum class FileActionType {
|
||||||
CREATE_AND_READ, READ, WRITE, DELETE, EXISTS
|
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 android.content.Context
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import me.rhunk.snapenhance.Logger
|
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 me.rhunk.snapenhance.data.LocalePair
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ class LocaleWrapper {
|
|||||||
loadFromContext(context)
|
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 {
|
fun format(key: String, vararg args: Pair<String, String>): String {
|
||||||
return args.fold(get(key)) { acc, pair ->
|
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 android.content.Context
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
@ -6,8 +6,8 @@ import com.google.gson.JsonElement
|
|||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import me.rhunk.snapenhance.Constants
|
import me.rhunk.snapenhance.Constants
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
|
import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||||
import me.rhunk.snapmapper.Mapper
|
import me.rhunk.snapmapper.Mapper
|
||||||
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
||||||
import me.rhunk.snapmapper.impl.CallbackMapper
|
import me.rhunk.snapmapper.impl.CallbackMapper
|
||||||
@ -58,11 +58,8 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
|||||||
runCatching {
|
runCatching {
|
||||||
loadCached()
|
loadCached()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to load cached mappings", it)
|
|
||||||
delete()
|
delete()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Logger.debug("Mappings file does not exist")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +119,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
|||||||
}
|
}
|
||||||
write(result.toString().toByteArray())
|
write(result.toString().toByteArray())
|
||||||
}.also {
|
}.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.content.ContentValues
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
@ -1,6 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.core.config
|
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
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,11 +4,10 @@ import android.content.Context
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
|
||||||
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
|
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
|
||||||
import me.rhunk.snapenhance.core.config.impl.RootConfig
|
import me.rhunk.snapenhance.core.config.impl.RootConfig
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
@ -33,7 +32,6 @@ class ModConfig {
|
|||||||
runCatching {
|
runCatching {
|
||||||
loadConfig()
|
loadConfig()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to load config", it)
|
|
||||||
writeConfig()
|
writeConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package me.rhunk.snapenhance.core.config.impl
|
package me.rhunk.snapenhance.core.config.impl
|
||||||
|
|
||||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||||
|
import me.rhunk.snapenhance.core.config.FeatureNotice
|
||||||
|
|
||||||
class Experimental : ConfigContainer() {
|
class Experimental : ConfigContainer() {
|
||||||
val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" }
|
val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" }
|
||||||
@ -9,6 +10,6 @@ class Experimental : ConfigContainer() {
|
|||||||
val appLockOnResume = boolean("app_lock_on_resume")
|
val appLockOnResume = boolean("app_lock_on_resume")
|
||||||
val infiniteStoryBoost = boolean("infinite_story_boost")
|
val infiniteStoryBoost = boolean("infinite_story_boost")
|
||||||
val meoPasscodeBypass = boolean("meo_passcode_bypass")
|
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")
|
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.annotation.SuppressLint
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import me.rhunk.snapenhance.Logger
|
import me.rhunk.snapenhance.Logger
|
||||||
import me.rhunk.snapenhance.ModContext
|
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 me.rhunk.snapenhance.manager.Manager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -68,7 +72,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
|||||||
try {
|
try {
|
||||||
obj.write(cursor)
|
obj.write(cursor)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.xposedLog(e)
|
context.log.error("Failed to read database object", e)
|
||||||
}
|
}
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return obj
|
return obj
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.database
|
package me.rhunk.snapenhance.core.database
|
||||||
|
|
||||||
import android.database.Cursor
|
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.annotation.SuppressLint
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import me.rhunk.snapenhance.Constants
|
import me.rhunk.snapenhance.Constants
|
||||||
|
import me.rhunk.snapenhance.core.database.DatabaseObject
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
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.getBlobOrNull
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getLong
|
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.annotation.SuppressLint
|
||||||
import android.database.Cursor
|
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.getIntOrNull
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getLong
|
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.annotation.SuppressLint
|
||||||
import android.database.Cursor
|
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.SerializableDataObject
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getLong
|
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.annotation.SuppressLint
|
||||||
import android.database.Cursor
|
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.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
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.annotation.SuppressLint
|
||||||
import android.database.Cursor
|
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.getInteger
|
||||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
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.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||||
import me.rhunk.snapenhance.download.data.DashOptions
|
import me.rhunk.snapenhance.core.download.data.DashOptions
|
||||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.download.data.DownloadRequest
|
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
||||||
import me.rhunk.snapenhance.download.data.InputMedia
|
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||||
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
|
import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
|
||||||
|
|
||||||
class DownloadManagerClient (
|
class DownloadManagerClient (
|
||||||
private val context: ModContext,
|
private val context: ModContext,
|
||||||
@ -33,10 +33,12 @@ class DownloadManagerClient (
|
|||||||
fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) {
|
fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) {
|
||||||
enqueueDownloadRequest(
|
enqueueDownloadRequest(
|
||||||
DownloadRequest(
|
DownloadRequest(
|
||||||
inputMedias = arrayOf(InputMedia(
|
inputMedias = arrayOf(
|
||||||
|
InputMedia(
|
||||||
content = playlistUrl,
|
content = playlistUrl,
|
||||||
type = DownloadMediaType.REMOTE_MEDIA
|
type = DownloadMediaType.REMOTE_MEDIA
|
||||||
)),
|
)
|
||||||
|
),
|
||||||
dashOptions = DashOptions(offsetTime, duration),
|
dashOptions = DashOptions(offsetTime, duration),
|
||||||
flags = DownloadRequest.Flags.IS_DASH_PLAYLIST
|
flags = DownloadRequest.Flags.IS_DASH_PLAYLIST
|
||||||
)
|
)
|
||||||
@ -46,11 +48,13 @@ class DownloadManagerClient (
|
|||||||
fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) {
|
fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) {
|
||||||
enqueueDownloadRequest(
|
enqueueDownloadRequest(
|
||||||
DownloadRequest(
|
DownloadRequest(
|
||||||
inputMedias = arrayOf(InputMedia(
|
inputMedias = arrayOf(
|
||||||
|
InputMedia(
|
||||||
content = mediaData,
|
content = mediaData,
|
||||||
type = mediaType,
|
type = mediaType,
|
||||||
encryption = encryption
|
encryption = encryption
|
||||||
))
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
package me.rhunk.snapenhance.download
|
package me.rhunk.snapenhance.core.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
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
|
import android.net.Uri
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.download.data
|
package me.rhunk.snapenhance.core.download.data
|
||||||
|
|
||||||
data class DownloadMetadata(
|
data class DownloadMetadata(
|
||||||
val mediaIdentifier: String?,
|
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 kotlinx.coroutines.Job
|
||||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
import me.rhunk.snapenhance.core.download.DownloadTaskManager
|
||||||
|
|
||||||
data class DownloadObject(
|
data class DownloadObject(
|
||||||
var downloadId: Int = 0,
|
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?)
|
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(
|
enum class DownloadStage(
|
||||||
val isFinalStage: Boolean = false,
|
val isFinalStage: Boolean = false,
|
@ -1,6 +1,6 @@
|
|||||||
@file:OptIn(ExperimentalEncodingApi::class)
|
@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 me.rhunk.snapenhance.data.wrapper.impl.media.EncryptionWrapper
|
||||||
import kotlin.io.encoding.Base64
|
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(
|
enum class MediaFilter(
|
||||||
val key: String,
|
val key: String,
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.download.data
|
package me.rhunk.snapenhance.core.download.data
|
||||||
|
|
||||||
enum class SplitMediaAssetType {
|
enum class SplitMediaAssetType {
|
||||||
ORIGINAL, OVERLAY
|
ORIGINAL, OVERLAY
|
@ -1,6 +1,5 @@
|
|||||||
package me.rhunk.snapenhance.core.eventbus
|
package me.rhunk.snapenhance.core.eventbus
|
||||||
|
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ interface IListener<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EventBus(
|
class EventBus(
|
||||||
private val context: ModContext
|
val context: ModContext
|
||||||
) {
|
) {
|
||||||
private val subscribers = mutableMapOf<KClass<out Event>, MutableList<IListener<out Event>>>()
|
private val subscribers = mutableMapOf<KClass<out Event>, MutableList<IListener<out Event>>>()
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ class EventBus(
|
|||||||
runCatching {
|
runCatching {
|
||||||
listener(event)
|
listener(event)
|
||||||
}.onFailure {
|
}.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 {
|
runCatching {
|
||||||
(listener as IListener<T>).handle(event)
|
(listener as IListener<T>).handle(event)
|
||||||
}.onFailure { t ->
|
}.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
|
return event
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.features
|
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.BufferedReader
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
@ -10,7 +10,6 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
@ -36,7 +35,7 @@ class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVI
|
|||||||
runCatching {
|
runCatching {
|
||||||
checkForUpdates()
|
checkForUpdates()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to check for updates: ${it.message}", it)
|
context.log.error("Failed to check for updates: ${it.message}", it)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
context.bridgeClient.setAutoUpdaterTime(currentTimeMillis)
|
context.bridgeClient.setAutoUpdaterTime(currentTimeMillis)
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,15 @@ import android.graphics.BitmapFactory
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import kotlinx.coroutines.runBlocking
|
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.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.core.messaging.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.FileType
|
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.dash.SnapPlaylistItem
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer
|
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.ParamMap
|
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.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
import me.rhunk.snapenhance.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
@ -84,19 +82,19 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
callback = object: DownloadCallback.Stub() {
|
callback = object: DownloadCallback.Stub() {
|
||||||
override fun onSuccess(outputFile: String) {
|
override fun onSuccess(outputFile: String) {
|
||||||
if (!downloadLogging.contains("success")) return
|
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("/")))
|
context.shortToast(context.translation.format("download_processor.saved_toast", "path" to outputFile.split("/").takeLast(2).joinToString("/")))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgress(message: String) {
|
override fun onProgress(message: String) {
|
||||||
if (!downloadLogging.contains("progress")) return
|
if (!downloadLogging.contains("progress")) return
|
||||||
Logger.debug("onProgress: message=$message")
|
context.log.verbose("onProgress: message=$message")
|
||||||
context.shortToast(message)
|
context.shortToast(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(message: String, throwable: String?) {
|
override fun onFailure(message: String, throwable: String?) {
|
||||||
if (!downloadLogging.contains("failure")) return
|
if (!downloadLogging.contains("failure")) return
|
||||||
Logger.debug("onFailure: message=$message, throwable=$throwable")
|
context.log.verbose("onFailure: message=$message, throwable=$throwable")
|
||||||
throwable?.let {
|
throwable?.let {
|
||||||
context.longToast((message + it.takeIf { it.isNotEmpty() }.orEmpty()))
|
context.longToast((message + it.takeIf { it.isNotEmpty() }.orEmpty()))
|
||||||
return
|
return
|
||||||
@ -402,8 +400,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
try {
|
try {
|
||||||
handleOperaMedia(mediaParamMap, mediaInfoMap, false)
|
handleOperaMedia(mediaParamMap, mediaInfoMap, false)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
xposedLog(e)
|
context.log.error("Failed to handle opera media", e)
|
||||||
context.longToast(e.message!!)
|
context.longToast(e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,11 +522,11 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
context.shortToast(translations["failed_to_create_preview_toast"])
|
context.shortToast(translations["failed_to_create_preview_toast"])
|
||||||
xposedLog(it)
|
context.log.error("Failed to create preview", it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
context.longToast(translations["failed_generic_toast"])
|
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.annotation.SuppressLint
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.RelativeLayout
|
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.AddViewEvent
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
@ -45,7 +44,7 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader", loadParams
|
|||||||
friendUsername!!
|
friendUsername!!
|
||||||
)
|
)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("Failed to download profile picture", it)
|
this@ProfilePictureDownloader.context.log.error("Failed to download profile picture", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.experiments
|
package me.rhunk.snapenhance.features.impl.experiments
|
||||||
|
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
@ -17,11 +16,11 @@ class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParam
|
|||||||
val fingerprintClass = android.os.Build::class.java
|
val fingerprintClass = android.os.Build::class.java
|
||||||
Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter ->
|
Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter ->
|
||||||
hookAdapter.setResult(fingerprint)
|
hookAdapter.setResult(fingerprint)
|
||||||
Logger.debug("Fingerprint spoofed to $fingerprint")
|
context.log.verbose("Fingerprint spoofed to $fingerprint")
|
||||||
}
|
}
|
||||||
Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter ->
|
Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter ->
|
||||||
hookAdapter.setResult(fingerprint)
|
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 ->
|
Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter ->
|
||||||
if(hookAdapter.args()[1] == "android_id") {
|
if(hookAdapter.args()[1] == "android_id") {
|
||||||
hookAdapter.setResult(androidId)
|
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
|
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.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||||
import me.rhunk.snapenhance.data.NotificationType
|
import me.rhunk.snapenhance.data.NotificationType
|
||||||
import me.rhunk.snapenhance.features.Feature
|
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
|
val associatedType = NotificationType.fromContentType(contentType) ?: return@subscribe
|
||||||
|
|
||||||
if (preventMessageSending.contains(associatedType.key)) {
|
if (preventMessageSending.contains(associatedType.key)) {
|
||||||
Logger.debug("Preventing message sending for $associatedType")
|
context.log.verbose("Preventing message sending for $associatedType")
|
||||||
event.canceled = true
|
event.canceled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.spying
|
package me.rhunk.snapenhance.features.impl.spying
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.spying
|
|||||||
import android.os.DeadObjectException
|
import android.os.DeadObjectException
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.MessageState
|
import me.rhunk.snapenhance.data.MessageState
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||||
@ -72,7 +71,7 @@ class MessageLogger : Feature("MessageLogger",
|
|||||||
context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
|
context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
|
||||||
fetchedMessages.addAll(context.bridgeClient.getLoggedMessageIds(friendFeedInfo.key!!, PREFETCH_MESSAGE_COUNT).toList())
|
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) {
|
private fun processSnapMessage(messageInstance: Any) {
|
||||||
|
@ -41,7 +41,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
|
|
||||||
val callback = CallbackBuilder(callbackClass)
|
val callback = CallbackBuilder(callbackClass)
|
||||||
.override("onError") {
|
.override("onError") {
|
||||||
Logger.xposedLog("Error saving message $messageId")
|
context.log.warn("Error saving message $messageId")
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.tweaks
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.FileObserver
|
import android.os.FileObserver
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
@ -32,7 +31,7 @@ class DisableVideoLengthRestriction : Feature("DisableVideoLengthRestriction", l
|
|||||||
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
|
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
|
||||||
if (fileContent["timerOrDuration"].asLong < 0) file.delete()
|
if (fileContent["timerOrDuration"].asLong < 0) file.delete()
|
||||||
}.onFailure {
|
}.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
|
package me.rhunk.snapenhance.features.impl.tweaks
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
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
|
findClass("com.google.android.gms.common.GoogleApiAvailability").methods
|
||||||
.first { Modifier.isStatic(it.modifiers) && it.returnType == AlertDialog::class.java }.let { method ->
|
.first { Modifier.isStatic(it.modifiers) && it.returnType == AlertDialog::class.java }.let { method ->
|
||||||
method.hook(HookStage.BEFORE) { param ->
|
method.hook(HookStage.BEFORE) { param ->
|
||||||
Logger.debug("GoogleApiAvailability.showErrorDialogFragment() called, returning null")
|
context.log.verbose("GoogleApiAvailability.showErrorDialogFragment() called, returning null")
|
||||||
param.setResult(null)
|
param.setResult(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ import android.os.UserHandle
|
|||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import me.rhunk.snapenhance.Logger
|
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.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
@ -297,7 +297,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
|||||||
fetchMessagesResult(conversationId, messageList)
|
fetchMessagesResult(conversationId, messageList)
|
||||||
}
|
}
|
||||||
.override("onError") {
|
.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()
|
}.build()
|
||||||
|
|
||||||
fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback)
|
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 intent = param.argNullable<Intent>(0) ?: return@hook
|
||||||
val messageType = intent.getStringExtra("type") ?: 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_", ""))) {
|
if (states.contains(messageType.replaceFirst("mischief_", ""))) {
|
||||||
param.setResult(null)
|
param.setResult(null)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.features.impl.ui
|
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.data.wrapper.impl.SnapUUID
|
||||||
import me.rhunk.snapenhance.features.BridgeFileFeature
|
import me.rhunk.snapenhance.features.BridgeFileFeature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
@ -1,38 +1,37 @@
|
|||||||
package me.rhunk.snapenhance.manager.impl
|
package me.rhunk.snapenhance.manager.impl
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import me.rhunk.snapenhance.action.AbstractAction
|
import me.rhunk.snapenhance.action.AbstractAction
|
||||||
import me.rhunk.snapenhance.action.impl.CheckForUpdates
|
import me.rhunk.snapenhance.action.EnumAction
|
||||||
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.manager.Manager
|
import me.rhunk.snapenhance.manager.Manager
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class ActionManager(
|
class ActionManager(
|
||||||
private val context: ModContext,
|
private val modContext: ModContext,
|
||||||
) : Manager {
|
) : Manager {
|
||||||
private val actions = mutableMapOf<String, AbstractAction>()
|
companion object {
|
||||||
fun getActions() = actions.values.toList()
|
const val ACTION_PARAMETER = "se_action"
|
||||||
private fun load(clazz: KClass<out AbstractAction>) {
|
|
||||||
val action = clazz.java.newInstance()
|
|
||||||
action.context = context
|
|
||||||
actions[action.nameKey] = action
|
|
||||||
}
|
}
|
||||||
|
private val actions = mutableMapOf<String, AbstractAction>()
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
load(CleanCache::class)
|
EnumAction.values().forEach { enumAction ->
|
||||||
load(ExportChatMessages::class)
|
actions[enumAction.key] = enumAction.clazz.java.getConstructor().newInstance().apply {
|
||||||
load(OpenMap::class)
|
this.context = modContext
|
||||||
load(CheckForUpdates::class)
|
}
|
||||||
if(BuildConfig.DEBUG) {
|
|
||||||
load(ClearMessageLogger::class)
|
|
||||||
load(RefreshMappings::class)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Button
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.Switch
|
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.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.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||||
@ -57,7 +56,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.xposedLog(e)
|
context.log.error("Error loading bitmoji selfie", e)
|
||||||
}
|
}
|
||||||
val finalIcon = icon
|
val finalIcon = icon
|
||||||
context.runOnUiThread {
|
context.runOnUiThread {
|
||||||
@ -243,7 +242,6 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
|
|
||||||
rules.forEach { ruleFeature ->
|
rules.forEach { ruleFeature ->
|
||||||
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
||||||
Logger.debug("${ruleFeature.ruleType.key} ${ruleFeature.getRuleState()}")
|
|
||||||
|
|
||||||
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
||||||
createToggleFeature(viewConsumer,
|
createToggleFeature(viewConsumer,
|
||||||
|
@ -8,7 +8,6 @@ import android.widget.Button
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
import me.rhunk.snapenhance.Constants
|
import me.rhunk.snapenhance.Constants
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme
|
import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme
|
||||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||||
@ -76,7 +75,7 @@ class OperaContextActionMenu : AbstractMenu() {
|
|||||||
linearLayout.addView(button)
|
linearLayout.addView(button)
|
||||||
(childView as ViewGroup).addView(linearLayout, 0)
|
(childView as ViewGroup).addView(linearLayout, 0)
|
||||||
} catch (e: Throwable) {
|
} 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.annotation.SuppressLint
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
|
||||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
|
||||||
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
import me.rhunk.snapenhance.ui.menu.AbstractMenu
|
||||||
|
|
||||||
class SettingsMenu : AbstractMenu() {
|
class SettingsMenu : AbstractMenu() {
|
||||||
//TODO: quick settings
|
//TODO: quick settings
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun inject(viewModel: View, addView: (View) -> Unit) {
|
fun inject(viewModel: View, addView: (View) -> Unit) {
|
||||||
val actions = context.actionManager.getActions().map {
|
/*val actions = context.actionManager.getActions().map {
|
||||||
Pair(it) {
|
Pair(it) {
|
||||||
val button = Button(viewModel.context)
|
val button = Button(viewModel.context)
|
||||||
button.text = context.translation[it.nameKey]
|
button.text = context.translation[it.nameKey]
|
||||||
@ -25,6 +23,6 @@ class SettingsMenu : AbstractMenu() {
|
|||||||
|
|
||||||
actions.forEach {
|
actions.forEach {
|
||||||
addView(it.second())
|
addView(it.second())
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ object SQLiteDatabaseHelper {
|
|||||||
|
|
||||||
if (newColumns.isEmpty()) return@forEach
|
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("DROP TABLE $tableName")
|
||||||
sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS $tableName (${columns.joinToString(", ")})")
|
sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS $tableName (${columns.joinToString(", ")})")
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class HttpServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
Logger.debug("starting http server on port $port")
|
Logger.directDebug("starting http server on port $port")
|
||||||
serverSocket = ServerSocket(port)
|
serverSocket = ServerSocket(port)
|
||||||
callback(this@HttpServer)
|
callback(this@HttpServer)
|
||||||
while (!serverSocket!!.isClosed) {
|
while (!serverSocket!!.isClosed) {
|
||||||
@ -48,21 +48,21 @@ class HttpServer(
|
|||||||
handleRequest(socket)
|
handleRequest(socket)
|
||||||
timeoutJob = launch {
|
timeoutJob = launch {
|
||||||
delay(timeout.toLong())
|
delay(timeout.toLong())
|
||||||
Logger.debug("http server closed due to timeout")
|
Logger.directDebug("http server closed due to timeout")
|
||||||
runCatching {
|
runCatching {
|
||||||
socketJob?.cancel()
|
socketJob?.cancel()
|
||||||
socket.close()
|
socket.close()
|
||||||
serverSocket?.close()
|
serverSocket?.close()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error(it)
|
Logger.directError("failed to close socket", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: SocketException) {
|
} catch (e: SocketException) {
|
||||||
Logger.debug("http server timed out")
|
Logger.directDebug("http server timed out")
|
||||||
break;
|
break;
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.error("failed to handle request", e)
|
Logger.directError("failed to handle request", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also { socketJob = it }
|
}.also { socketJob = it }
|
||||||
@ -90,13 +90,13 @@ class HttpServer(
|
|||||||
outputStream.close()
|
outputStream.close()
|
||||||
socket.close()
|
socket.close()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.error("failed to close socket", it)
|
Logger.directError("failed to close socket", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val parse = StringTokenizer(line)
|
val parse = StringTokenizer(line)
|
||||||
val method = parse.nextToken().uppercase(Locale.getDefault())
|
val method = parse.nextToken().uppercase(Locale.getDefault())
|
||||||
var fileRequested = parse.nextToken().lowercase(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") {
|
if (method != "GET") {
|
||||||
with(writer) {
|
with(writer) {
|
||||||
|
@ -44,7 +44,7 @@ object RemoteMediaResolver {
|
|||||||
|
|
||||||
okHttpClient.newCall(request).execute().use { response ->
|
okHttpClient.newCall(request).execute().use { response ->
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
Logger.log("Unexpected code $response")
|
Logger.directDebug("Unexpected code $response")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return ByteArrayInputStream(response.body.bytes())
|
return ByteArrayInputStream(response.body.bytes())
|
||||||
|
@ -9,16 +9,15 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
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.ContentType
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
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.protobuf.ProtoReader
|
||||||
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
||||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||||
@ -138,7 +137,7 @@ class MessageExporter(
|
|||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
printLog("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}")
|
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 {
|
}.onFailure {
|
||||||
printLog("failed to read template from apk")
|
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())
|
output.write("</html>".toByteArray())
|
||||||
|
@ -4,9 +4,9 @@ import com.arthenica.ffmpegkit.FFmpegKit
|
|||||||
import com.arthenica.ffmpegkit.FFmpegSession
|
import com.arthenica.ffmpegkit.FFmpegSession
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import me.rhunk.snapenhance.Constants
|
import me.rhunk.snapenhance.Constants
|
||||||
|
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
||||||
import me.rhunk.snapenhance.data.ContentType
|
import me.rhunk.snapenhance.data.ContentType
|
||||||
import me.rhunk.snapenhance.data.FileType
|
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.download.RemoteMediaResolver
|
||||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
Loading…
x
Reference in New Issue
Block a user