ui: home screen

- monochrome launcher icon
This commit is contained in:
rhunk
2023-07-28 22:20:41 +02:00
parent 304bd9fe94
commit e4cab94ec7
29 changed files with 607 additions and 146 deletions

View File

@ -91,7 +91,6 @@ class BridgeClient(
fun deleteFile(fileType: BridgeFileType) = service.deleteFile(fileType.value)
fun isFileExists(fileType: BridgeFileType) = service.isFileExists(fileType.value)
fun getLoggedMessageIds(conversationId: String, limit: Int): LongArray = service.getLoggedMessageIds(conversationId, limit)

View File

@ -0,0 +1,35 @@
package me.rhunk.snapenhance.bridge
import android.content.Context
import me.rhunk.snapenhance.bridge.types.BridgeFileType
open class FileLoaderWrapper(
private val fileType: BridgeFileType,
private val defaultContent: ByteArray
) {
lateinit var isFileExists: () -> Boolean
lateinit var write: (ByteArray) -> Unit
lateinit var read: () -> ByteArray
lateinit var delete: () -> Unit
fun loadFromContext(context: Context) {
val file = fileType.resolve(context)
isFileExists = { file.exists() }
read = {
if (!file.exists()) {
file.createNewFile()
file.writeBytes("{}".toByteArray(Charsets.UTF_8))
}
file.readBytes()
}
write = { file.writeBytes(it) }
delete = { file.delete() }
}
fun loadFromBridge(bridgeClient: BridgeClient) {
isFileExists = { bridgeClient.isFileExists(fileType) }
read = { bridgeClient.createAndReadFile(fileType, defaultContent) }
write = { bridgeClient.writeFile(fileType, it) }
delete = { bridgeClient.deleteFile(fileType) }
}
}

View File

@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.bridge.BridgeClient
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.bridge.types.BridgeFileType
import me.rhunk.snapenhance.config.ConfigAccessor
import me.rhunk.snapenhance.config.ConfigProperty
@ -14,16 +15,14 @@ class ConfigWrapper: ConfigAccessor() {
private val gson = GsonBuilder().setPrettyPrinting().create()
}
private lateinit var isFileExistsAction: () -> Boolean
private lateinit var writeFileAction: (ByteArray) -> Unit
private lateinit var readFileAction: () -> ByteArray
private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8))
fun load() {
ConfigProperty.sortedByCategory().forEach { key ->
set(key, key.valueContainer)
}
if (!isFileExistsAction()) {
if (!file.isFileExists()) {
writeConfig()
return
}
@ -37,7 +36,7 @@ class ConfigWrapper: ConfigAccessor() {
}
private fun loadConfig() {
val configContent = readFileAction()
val configContent = file.read()
val configObject: JsonObject = gson.fromJson(
configContent.toString(Charsets.UTF_8),
@ -54,27 +53,17 @@ class ConfigWrapper: ConfigAccessor() {
entries().forEach { (key, value) ->
configObject.addProperty(key.name, value.read())
}
writeFileAction(gson.toJson(configObject).toByteArray(Charsets.UTF_8))
file.write(gson.toJson(configObject).toByteArray(Charsets.UTF_8))
}
fun loadFromContext(context: Context) {
val configFile = BridgeFileType.CONFIG.resolve(context)
isFileExistsAction = { configFile.exists() }
readFileAction = {
if (!configFile.exists()) {
configFile.createNewFile()
configFile.writeBytes("{}".toByteArray(Charsets.UTF_8))
}
configFile.readBytes()
}
writeFileAction = { configFile.writeBytes(it) }
file.loadFromContext(context)
load()
}
fun loadFromBridge(bridgeClient: BridgeClient) {
isFileExistsAction = { bridgeClient.isFileExists(BridgeFileType.CONFIG) }
readFileAction = { bridgeClient.createAndReadFile(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8)) }
writeFileAction = { bridgeClient.writeFile(BridgeFileType.CONFIG, it) }
file.loadFromBridge(bridgeClient)
load()
}
}

View File

@ -0,0 +1,157 @@
package me.rhunk.snapenhance.bridge.wrapper
import android.content.Context
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.bridge.types.BridgeFileType
import me.rhunk.snapmapper.Mapper
import me.rhunk.snapmapper.impl.BCryptClassMapper
import me.rhunk.snapmapper.impl.CallbackMapper
import me.rhunk.snapmapper.impl.DefaultMediaItemMapper
import me.rhunk.snapmapper.impl.EnumMapper
import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper
import me.rhunk.snapmapper.impl.MediaQualityLevelProviderMapper
import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper
import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper
import me.rhunk.snapmapper.impl.PlusSubscriptionMapper
import me.rhunk.snapmapper.impl.ScCameraSettingsMapper
import me.rhunk.snapmapper.impl.StoryBoostStateMapper
import java.util.concurrent.ConcurrentHashMap
import kotlin.system.measureTimeMillis
class MappingsWrapper(
private val context: Context,
) : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteArray(Charsets.UTF_8)) {
companion object {
private val gson = GsonBuilder().setPrettyPrinting().create()
private val mappers = arrayOf(
BCryptClassMapper::class,
CallbackMapper::class,
DefaultMediaItemMapper::class,
MediaQualityLevelProviderMapper::class,
EnumMapper::class,
OperaPageViewControllerMapper::class,
PlatformAnalyticsCreatorMapper::class,
PlusSubscriptionMapper::class,
ScCameraSettingsMapper::class,
StoryBoostStateMapper::class,
FriendsFeedEventDispatcherMapper::class
)
}
private val mappings = ConcurrentHashMap<String, Any>()
private var snapBuildNumber: Long = 0
@Suppress("deprecation")
fun init() {
snapBuildNumber = getSnapchatVersionCode()
if (isFileExists()) {
runCatching {
loadCached()
}.onFailure {
delete()
}
}
}
fun getSnapchatPackageInfo() = runCatching {
context.packageManager.getPackageInfo(
Constants.SNAPCHAT_PACKAGE_NAME,
0
)
}.getOrNull()
fun getSnapchatVersionCode() = getSnapchatPackageInfo()?.longVersionCode ?: -1
fun getApplicationSourceDir() = getSnapchatPackageInfo()?.applicationInfo?.sourceDir
fun getGeneratedBuildNumber() = snapBuildNumber
fun isMappingsOutdated(): Boolean {
return snapBuildNumber != getSnapchatVersionCode() || isMappingsLoaded().not()
}
fun isMappingsLoaded(): Boolean {
return mappings.isNotEmpty()
}
private fun loadCached() {
if (!isFileExists()) {
throw Exception("Mappings file does not exist")
}
val mappingsObject = JsonParser.parseString(read().toString(Charsets.UTF_8)).asJsonObject.also {
snapBuildNumber = it["snap_build_number"].asLong
}
mappingsObject.entrySet().forEach { (key, value): Map.Entry<String, JsonElement> ->
if (value.isJsonArray) {
mappings[key] = gson.fromJson(value, ArrayList::class.java)
return@forEach
}
if (value.isJsonObject) {
mappings[key] = gson.fromJson(value, ConcurrentHashMap::class.java)
return@forEach
}
mappings[key] = value.asString
}
}
fun refresh() {
val mapper = Mapper(*mappers)
runCatching {
mapper.loadApk(getApplicationSourceDir() ?: throw Exception("Failed to get APK"))
}.onFailure {
throw Exception("Failed to load APK", it)
}
measureTimeMillis {
val result = mapper.start().apply {
addProperty("snap_build_number", snapBuildNumber)
}
write(result.toString().toByteArray())
}.also {
Logger.xposedLog("Generated mappings in $it ms")
}
}
fun getMappedObject(key: String): Any {
if (mappings.containsKey(key)) {
return mappings[key]!!
}
throw Exception("No mapping found for $key")
}
fun getMappedObjectNullable(key: String): Any? {
return mappings[key]
}
fun getMappedClass(className: String): Class<*> {
return context.classLoader.loadClass(getMappedObject(className) as String)
}
fun getMappedClass(key: String, subKey: String): Class<*> {
return context.classLoader.loadClass(getMappedValue(key, subKey))
}
fun getMappedValue(key: String): String {
return getMappedObject(key) as String
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> getMappedList(key: String): List<T> {
return listOf(getMappedObject(key) as List<T>).flatten()
}
fun getMappedValue(key: String, subKey: String): String {
return getMappedMap(key)[subKey] as String
}
@Suppress("UNCHECKED_CAST")
fun getMappedMap(key: String): Map<String, *> {
return getMappedObject(key) as Map<String, *>
}
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/launcher_icon_background"/>
<foreground android:drawable="@mipmap/launcher_icon_foreground"/>
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/launcher_icon_background"/>
<foreground android:drawable="@mipmap/launcher_icon_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB