commit e4cab94ec789e6eb52e3f683ab148e11bbec9435
parent 304bd9fe943159ba2fc2db5ff9970b7bec698cf0
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Fri, 28 Jul 2023 22:20:41 +0200

ui: home screen
- monochrome launcher icon

Diffstat:
Mapp/build.gradle.kts | 4++++
Mapp/src/main/AndroidManifest.xml | 12++++++------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt | 32+++++++++-----------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt | 158++++++++++++++++++++++++++++++++++++-------------------------------------------
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Theme.kt | 2--
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/data/InstallationSummary.kt | 17+++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/data/ManagerContext.kt | 36++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/DebugSection.kt | 5+++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/HomeSection.kt | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt | 16+++++++++-------
Aapp/src/main/res/drawable/launcher_icon_monochrome.xml | 26++++++++++++++++++++++++++
Aapp/src/main/res/mipmap-anydpi-v26/launcher_icon.xml | 7+++++++
Rcore/src/main/res/mipmap-anydpi-v26/launcher_icon.xml -> app/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml | 0
Rcore/src/main/res/mipmap-hdpi/launcher_icon_foreground.png -> app/src/main/res/mipmap-hdpi/launcher_icon_foreground.png | 0
Rcore/src/main/res/mipmap-hdpi/launcher_icon_round.png -> app/src/main/res/mipmap-hdpi/launcher_icon_round.png | 0
Rcore/src/main/res/mipmap-mdpi/launcher_icon_foreground.png -> app/src/main/res/mipmap-mdpi/launcher_icon_foreground.png | 0
Rcore/src/main/res/mipmap-mdpi/launcher_icon_round.png -> app/src/main/res/mipmap-mdpi/launcher_icon_round.png | 0
Rcore/src/main/res/mipmap-xhdpi/launcher_icon_foreground.png -> app/src/main/res/mipmap-xhdpi/launcher_icon_foreground.png | 0
Rcore/src/main/res/mipmap-xhdpi/launcher_icon_round.png -> app/src/main/res/mipmap-xhdpi/launcher_icon_round.png | 0
Rcore/src/main/res/mipmap-xxhdpi/launcher_icon_foreground.png -> app/src/main/res/mipmap-xxhdpi/launcher_icon_foreground.png | 0
Rcore/src/main/res/mipmap-xxhdpi/launcher_icon_round.png -> app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png | 0
Rcore/src/main/res/mipmap-xxxhdpi/launcher_icon_foreground.png -> app/src/main/res/mipmap-xxxhdpi/launcher_icon_foreground.png | 0
Rcore/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png -> app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png | 0
Mcore/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeClient.kt | 1-
Acore/src/main/kotlin/me/rhunk/snapenhance/bridge/FileLoaderWrapper.kt | 36++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/ConfigWrapper.kt | 27++++++++-------------------
Acore/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/MappingsWrapper.kt | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dcore/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml | 6------
30 files changed, 617 insertions(+), 150 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts @@ -85,6 +85,10 @@ dependencies { implementation(libs.androidx.activity.ktx) implementation(libs.androidx.material3) implementation(libs.androidx.material) + + debugImplementation("androidx.compose.ui:ui-tooling:1.4.3") + implementation("androidx.compose.ui:ui-tooling-preview:1.4.3") + implementation(kotlin("reflect")) } afterEvaluate { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml @@ -7,16 +7,16 @@ <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" - tools:ignore="ScopedStorage" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" - tools:ignore="ScopedStorage" /> + <queries> + <package android:name="com.snapchat.android" /> + </queries> + <application android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true" android:label="@string/app_name" - tools:targetApi="31" + tools:targetApi="34" + android:enableOnBackInvokedCallback="true" android:icon="@mipmap/launcher_icon"> <meta-data android:name="xposedmodule" diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt @@ -16,10 +16,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController +import me.rhunk.snapenhance.manager.data.ManagerContext class MainActivity : ComponentActivity() { @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @@ -27,39 +29,23 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - App() + App(ManagerContext(this)) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun App() { +fun App( + context: ManagerContext +) { val navController = rememberNavController() + val navigation = Navigation(context) AppMaterialTheme { Scaffold( - topBar = { - TopAppBar( - title = { Text(text = "SnapEnhance") }, - actions = { - IconButton(onClick = { /*TODO*/ }) { - Icon(Icons.Filled.Settings, contentDescription = "Settings") - } - } - ) - }, containerColor = MaterialTheme.colorScheme.background, - bottomBar = { NavBar(navController = navController) } + bottomBar = { navigation.NavBar(navController = navController) } ) { innerPadding -> - NavHost(navController, startDestination = "main", Modifier.padding(innerPadding)) { - navigation(MainSections.HOME.route, "main") { - MainSections.values().toList().forEach { section -> - composable(section.route) { - section.content() - } - } - } - } + navigation.NavigationHost(navController = navController, innerPadding = innerPadding) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt @@ -1,113 +1,100 @@ package me.rhunk.snapenhance.manager import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredWidth -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.BugReport -import androidx.compose.material.icons.filled.Download -import androidx.compose.material.icons.filled.Group -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Stars import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState -import me.rhunk.snapenhance.manager.sections.NotImplemented +import me.rhunk.snapenhance.manager.data.ManagerContext -enum class MainSections( - val route: String, - val title: String, - val icon: ImageVector, - val content: @Composable () -> Unit +class Navigation( + private val context: ManagerContext ) { - DOWNLOADS( - route = "downloads", - title = "Downloads", - icon = Icons.Filled.Download, - content = { NotImplemented() } - ), - FEATURES( - route = "features", - title = "Features", - icon = Icons.Filled.Stars, - content = { NotImplemented() } - ), - HOME( - route = "home", - title = "Home", - icon = Icons.Filled.Home, - content = { NotImplemented() } - ), - FRIENDS( - route = "friends", - title = "Friends", - icon = Icons.Filled.Group, - content = { NotImplemented() } - ), - DEBUG( - route = "debug", - title = "Debug", - icon = Icons.Filled.BugReport, - content = { NotImplemented() } - ); -} + @Composable + fun NavigationHost( + navController: NavHostController, + innerPadding: PaddingValues + ) { + val sections = remember { EnumSection.values().toList().map { + it to it.section.constructors.first().call() + }.onEach { (_, instance) -> + instance.manager = context + instance.navController = navController + } } + val homeSection = EnumSection.HOME -@Composable -fun NavBar( - navController: NavController -) { - NavigationBar { - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - MainSections.values().toList().forEach { section -> - fun selected() = currentDestination?.hierarchy?.any { it.route == section.route } == true + NavHost(navController, startDestination = homeSection.route, Modifier.padding(innerPadding)) { + sections.forEach { (section, instance) -> + composable(section.route) { + instance.Content() + } + } + } + } + + @Composable + fun NavBar( + navController: NavController + ) { + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + EnumSection.values().toList().forEach { section -> + fun selected() = currentDestination?.hierarchy?.any { it.route == section.route } == true - NavigationBarItem( - modifier = Modifier - .requiredWidth(120.dp) - .fillMaxHeight(), - icon = { - val iconOffset by animateDpAsState( - if (selected()) 0.dp else 10.dp, - label = "" - ) - Icon( - imageVector = section.icon, - contentDescription = null, - modifier = Modifier.offset(y = iconOffset) - ) - }, + NavigationBarItem( + modifier = Modifier + .requiredWidth(120.dp) + .fillMaxHeight(), + icon = { + val iconOffset by animateDpAsState( + if (selected()) 0.dp else 10.dp, + label = "" + ) + Icon( + imageVector = section.icon, + contentDescription = null, + modifier = Modifier.offset(y = iconOffset) + ) + }, - label = { - val labelOffset by animateDpAsState( - if (selected()) 0.dp else (-5).dp, - label = "" - ) - Text(text = if (selected()) section.title else "", modifier = Modifier.offset(y = labelOffset)) - }, - selected = selected(), - onClick = { - navController.navigate(section.route) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true + label = { + val labelOffset by animateDpAsState( + if (selected()) 0.dp else (-5).dp, + label = "" + ) + Text(text = if (selected()) section.title else "", modifier = Modifier.offset(y = labelOffset)) + }, + selected = selected(), + onClick = { + navController.navigate(section.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true } - launchSingleTop = true - restoreState = true } - } - ) + ) + } } } -}- \ No newline at end of file +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt @@ -0,0 +1,61 @@ +package me.rhunk.snapenhance.manager + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BugReport +import androidx.compose.material.icons.filled.Download +import androidx.compose.material.icons.filled.Group +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Stars +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.navigation.NavController +import me.rhunk.snapenhance.manager.data.ManagerContext +import me.rhunk.snapenhance.manager.sections.FeaturesSection +import me.rhunk.snapenhance.manager.sections.HomeSection +import me.rhunk.snapenhance.manager.sections.NotImplemented +import kotlin.reflect.KClass + +enum class EnumSection( + val route: String, + val title: String, + val icon: ImageVector, + val section: KClass<out Section> = NotImplemented::class +) { + DOWNLOADS( + route = "downloads", + title = "Downloads", + icon = Icons.Filled.Download + ), + FEATURES( + route = "features", + title = "Features", + icon = Icons.Filled.Stars, + section = FeaturesSection::class + ), + HOME( + route = "home", + title = "Home", + icon = Icons.Filled.Home, + section = HomeSection::class + ), + FRIENDS( + route = "friends", + title = "Friends", + icon = Icons.Filled.Group + ), + DEBUG( + route = "debug", + title = "Debug", + icon = Icons.Filled.BugReport + ); +} + + + +open class Section { + lateinit var manager: ManagerContext + lateinit var navController: NavController + + @Composable + open fun Content() { NotImplemented() } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Theme.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Theme.kt @@ -74,8 +74,6 @@ val md_theme_dark_scrim = Color(0xFF000000) val seed = Color(0xFF6750A4) - - private val LightThemeColors = lightColorScheme( primary = md_theme_light_primary, onPrimary = md_theme_light_onPrimary, diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/data/InstallationSummary.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/data/InstallationSummary.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.manager.data + + +data class SnapchatAppInfo( + val version: String, + val versionCode: Long +) + +data class ModMappingsInfo( + val generatedSnapchatVersion: Long, + val isOutdated: Boolean +) + +data class InstallationSummary( + val snapchatInfo: SnapchatAppInfo?, + val mappingsInfo: ModMappingsInfo? +) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/data/ManagerContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/data/ManagerContext.kt @@ -0,0 +1,35 @@ +package me.rhunk.snapenhance.manager.data + +import android.content.Context +import me.rhunk.snapenhance.bridge.wrapper.ConfigWrapper +import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper +import me.rhunk.snapenhance.bridge.wrapper.TranslationWrapper + +class ManagerContext( + private val context: Context +) { + private val config = ConfigWrapper() + private val translation = TranslationWrapper() + private val mappings = MappingsWrapper(context) + + init { + config.loadFromContext(context) + translation.loadFromContext(context) + mappings.apply { loadFromContext(context) }.init() + } + + fun getInstallationSummary() = InstallationSummary( + snapchatInfo = mappings.getSnapchatPackageInfo()?.let { + SnapchatAppInfo( + version = it.versionName, + versionCode = it.longVersionCode + ) + }, + mappingsInfo = if (mappings.isMappingsLoaded()) { + ModMappingsInfo( + generatedSnapchatVersion = mappings.getGeneratedBuildNumber(), + isOutdated = mappings.isMappingsOutdated() + ) + } else null + ) +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/DebugSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/DebugSection.kt @@ -0,0 +1,4 @@ +package me.rhunk.snapenhance.manager.sections + +class DebugSection { +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt @@ -0,0 +1,52 @@ +package me.rhunk.snapenhance.manager.sections + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.rhunk.snapenhance.manager.Section + +class FeaturesSection : Section() { + @Composable + @Preview + override fun Content() { + Column { + Text( + text = "Features", + modifier = Modifier.padding(all = 15.dp), + fontSize = 20.sp + ) + LazyColumn( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center + ) { + items(100) { index -> + OutlinedCard( + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp) + .height(70.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center + ) { + Text(text = "Feature $index", modifier = Modifier.padding(all = 15.dp)) + } + } + } + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/HomeSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/HomeSection.kt @@ -0,0 +1,108 @@ +package me.rhunk.snapenhance.manager.sections + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +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.Map +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.rhunk.snapenhance.manager.Section +import me.rhunk.snapenhance.manager.data.InstallationSummary + +class HomeSection : Section() { + companion object { + val cardMargin = 10.dp + } + + @OptIn(ExperimentalLayoutApi::class) + @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() + ) { + FlowRow( + 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 = {}, modifier = Modifier.height(40.dp)) { + Icon(Icons.Filled.Refresh, contentDescription = "Refresh") + } + } + } + } + + @Composable + @Preview + override fun Content() { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(ScrollState(0)) + ) { + Text( + "SnapEnhance", + fontSize = 32.sp, + modifier = Modifier.padding(32.dp) + ) + + 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(manager.getInstallationSummary()) + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt @@ -1,18 +1,20 @@ package me.rhunk.snapenhance.manager.sections +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import me.rhunk.snapenhance.manager.Section -@Composable -fun NotImplemented() { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text(text = "Not Implemented") +class NotImplemented : Section() { + @Composable + override fun Content() { + Column { + Text(text = "Not implemented yet!") + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/launcher_icon_monochrome.xml b/app/src/main/res/drawable/launcher_icon_monochrome.xml @@ -0,0 +1,26 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:width="48dp" + android:height="48dp" + android:viewportWidth="1000" + android:viewportHeight="1000"> + + <group + android:scaleX="0.65" + android:scaleY="0.65" + android:translateX="175" + android:translateY="175"> + + <path + android:fillColor="#ffffffff" + android:pathData="m397.9,491.5h-55.1c-10.1,0 -18.4,8.2 -18.4,18.4h0c0,10.1 8.2,18.4 18.4,18.4h55.1c15.3,0 27.8,13.7 27.8,30.6s-12.5,30.6 -27.8,30.6h-55.1c-10.1,0 -18.4,8.2 -18.4,18.4h0c0,10.1 8.2,18.4 18.4,18.4h55.1c33.8,0 61.2,-30.1 61.2,-67.3s-27.4,-67.3 -61.2,-67.3Z"/> + <path + android:fillColor="#ffffffff" + android:pathData="m814.2,491.5h-62.6v-49.7h-0.1c0,-2 0.1,-4.1 0.1,-6.1 0,-152.1 -123.3,-275.5 -275.5,-275.5s-275.5,123.3 -275.5,275.5c0,2 0,4.1 0.1,6.1h-0.1v302.1c0,29.6 13.5,57.6 36.7,76l11.9,9.4c18.3,13.9 47,13.8 65.2,0l18.5,-14c7.1,-5.4 21,-5.4 28.1,0l18.5,14c18.3,13.9 46.9,13.9 65.2,0l18.5,-14c3.3,-2.5 8.2,-3.8 13.1,-4 4.9,0.2 9.7,1.5 13,4l18.5,14c18.3,13.8 46.8,13.8 65.1,0l18.5,-14c7.1,-5.4 20.9,-5.4 28,0l18.5,14c18.2,13.8 46.8,13.8 65.1,0l11.9,-9.4c23.2,-18.4 36.7,-46.4 36.7,-75.9v-117.8h62.6c33.8,0 61.2,-30.1 61.2,-67.3s-27.4,-67.3 -61.2,-67.3ZM714.8,441.9v310.2c0,16.4 -7.7,31.9 -20.7,41.8l-9.6,7.3c-7.1,5.4 -20.9,5.4 -28,0l-18.5,-14c-18.2,-13.8 -46.8,-13.8 -65.1,0l-18.5,14c-7.1,5.4 -20.9,5.4 -28,0l-18.5,-14c-8.8,-6.7 -20.1,-10.1 -31.4,-10.3v-0c-0.1,0 -0.1,0 -0.2,0 -0,0 -0.1,0 -0.1,0h0c-11.4,0.2 -22.6,3.7 -31.5,10.4l-18.5,14c-7.1,5.4 -21,5.4 -28.1,0l-18.5,-14c-18.3,-13.8 -46.9,-13.8 -65.2,0l-18.5,14c-7.1,5.4 -21,5.4 -28,0l-9.7,-7.4c-13.1,-9.9 -20.8,-25.4 -20.8,-41.8v-310.1h0.1c-0.1,-2 -0.1,-4.1 -0.1,-6.1 0,-131.9 106.9,-238.7 238.7,-238.7s238.7,106.9 238.7,238.7c0,2 -0,4.1 -0.1,6.1h0.1ZM814.2,589.5h-62.6v-61.2h62.6c15.3,0 27.8,13.7 27.8,30.6s-12.5,30.6 -27.8,30.6Z" + tools:ignore="VectorPath" /> + <path + android:fillColor="#ffffffff" + android:pathData="m711.1,336.1c-6.6,-1.6 -10.6,-1.7 -17.4,-2.8 -33.1,-5.4 -65.4,-0.8 -97.3,8.2 -7,2 -13.9,3.4 -20.9,3.5 -7,-0.2 -13.9,-1.5 -20.9,-3.5 -31.9,-9 -64.2,-13.6 -97.3,-8.2 -6.8,1.1 -10.8,1.1 -17.4,2.8 -6,1.5 -7.5,11.2 -5.5,29.1 0.9,8.2 3.3,16.1 4.9,24.2 1.6,8.1 3.6,16.1 7.9,23.3 4.1,6.8 10.1,11.6 17.5,13.9 16.9,5.4 34.1,6.2 51.3,1.4 14.5,-4.1 26.1,-12.6 33.4,-25.9 5.4,-9.9 9.7,-20.4 14.5,-30.7 0.7,-1.6 1.3,-3.2 2.1,-4.8 2.2,-4.4 5,-6.3 9.6,-6.3 4.5,-0 7.3,1.8 9.6,6.3 0.8,1.6 1.4,3.2 2.1,4.8 4.8,10.3 9.1,20.8 14.5,30.7 7.2,13.4 18.9,21.9 33.4,25.9 17.1,4.8 34.4,4 51.3,-1.4 7.4,-2.3 13.4,-7.1 17.5,-13.9 4.3,-7.2 6.3,-15.1 7.9,-23.3 1.5,-8.1 4,-16 4.9,-24.2 2,-17.9 0.5,-27.6 -5.5,-29.1Z"/> + + </group> +</vector> diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml @@ -0,0 +1,6 @@ +<?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"/> + <monochrome android:drawable="@drawable/launcher_icon_monochrome"/> +</adaptive-icon>+ \ No newline at end of file diff --git a/core/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/app/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml diff --git a/core/src/main/res/mipmap-hdpi/launcher_icon_foreground.png b/app/src/main/res/mipmap-hdpi/launcher_icon_foreground.png Binary files differ. diff --git a/core/src/main/res/mipmap-hdpi/launcher_icon_round.png b/app/src/main/res/mipmap-hdpi/launcher_icon_round.png Binary files differ. diff --git a/core/src/main/res/mipmap-mdpi/launcher_icon_foreground.png b/app/src/main/res/mipmap-mdpi/launcher_icon_foreground.png Binary files differ. diff --git a/core/src/main/res/mipmap-mdpi/launcher_icon_round.png b/app/src/main/res/mipmap-mdpi/launcher_icon_round.png Binary files differ. diff --git a/core/src/main/res/mipmap-xhdpi/launcher_icon_foreground.png b/app/src/main/res/mipmap-xhdpi/launcher_icon_foreground.png Binary files differ. diff --git a/core/src/main/res/mipmap-xhdpi/launcher_icon_round.png b/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png Binary files differ. diff --git a/core/src/main/res/mipmap-xxhdpi/launcher_icon_foreground.png b/app/src/main/res/mipmap-xxhdpi/launcher_icon_foreground.png Binary files differ. diff --git a/core/src/main/res/mipmap-xxhdpi/launcher_icon_round.png b/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png Binary files differ. diff --git a/core/src/main/res/mipmap-xxxhdpi/launcher_icon_foreground.png b/app/src/main/res/mipmap-xxxhdpi/launcher_icon_foreground.png Binary files differ. diff --git a/core/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png b/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png Binary files differ. diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeClient.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeClient.kt @@ -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) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/FileLoaderWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/FileLoaderWrapper.kt @@ -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) } + } +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/ConfigWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/ConfigWrapper.kt @@ -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() } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/MappingsWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/MappingsWrapper.kt @@ -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, *> + } +}+ \ No newline at end of file diff --git a/core/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml b/core/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml @@ -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>- \ No newline at end of file