package com.meowarex.rlmobile import android.content.Intent import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.compose.animation.core.* import androidx.compose.runtime.* import androidx.compose.ui.unit.IntOffset import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.compose.LifecycleResumeEffect import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.* import cafe.adriel.voyager.transitions.SlideTransition import com.meowarex.rlmobile.MainActivity.Companion.EXTRA_COMPONENT_TYPE import com.meowarex.rlmobile.MainActivity.Companion.EXTRA_FILE_PATH import com.meowarex.rlmobile.MainActivity.Companion.EXTRA_PACKAGE_NAME import com.meowarex.rlmobile.manager.* import com.meowarex.rlmobile.patcher.InstallMetadata import com.meowarex.rlmobile.ui.screens.home.HomeScreen import com.meowarex.rlmobile.ui.screens.patching.PatchingScreen import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions import com.meowarex.rlmobile.ui.screens.permissions.PermissionsModel import com.meowarex.rlmobile.ui.screens.permissions.PermissionsScreen import com.meowarex.rlmobile.ui.theme.ManagerTheme import com.meowarex.rlmobile.ui.widgets.updater.UpdaterDialog import com.meowarex.rlmobile.util.* import com.github.diamondminer88.zip.ZipReader import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import java.io.File class MainActivity : ComponentActivity() { private val permissions: PermissionsModel by viewModel() private val preferences: PreferencesManager by inject() private val overlays: OverlayManager by inject() private val paths: PathManager by inject() private val json: Json by inject() private val scope = CoroutineScope(Dispatchers.Default) override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) installSplashScreen() super.onCreate(savedInstanceState) setContent { // Refresh permissions when the activity resumes LifecycleResumeEffect(Unit) { permissions.refresh() onPauseOrDispose {} } ManagerTheme( theme = preferences.theme, dynamicColor = preferences.dynamicColor, ) { if (BuildConfig.RELEASE) { UpdaterDialog() } @OptIn(ExperimentalVoyagerApi::class) CompositionLocalProvider( LocalNavigatorSaver provides parcelableNavigatorSaver(), ) { Navigator( screen = HomeScreen(), onBackPressed = null, ) { navigator -> // Open the permissions screen whenever permissions are insufficient LaunchedEffect(permissions.requiredPermsGranted) { if (!permissions.requiredPermsGranted) navigator.pushOnce(PermissionsScreen()) } DisposableEffect(Unit) { this@MainActivity.intent?.let { handleNewIntent(it, navigator) } fun handle(intent: Intent) = handleNewIntent(intent, navigator) addOnNewIntentListener(::handle) onDispose { removeOnNewIntentListener(::handle) } } BackHandler { navigator.back(this@MainActivity) } SlideTransition( navigator = navigator, disposeScreenAfterTransitionEnd = true, animationSpec = spring( stiffness = Spring.StiffnessMedium, visibilityThreshold = IntOffset.VisibilityThreshold, ) ) } overlays.Overlays() } } } } private fun handleNewIntent(intent: Intent, navigator: Navigator) = scope.launchBlock { when (intent.action) { INTENT_REINSTALL -> { val packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: run { Log.w(BuildConfig.TAG, "Missing $EXTRA_PACKAGE_NAME extra for intent $INTENT_REINSTALL") return@launchBlock } navigator.push(handleReinstall(packageName)) } INTENT_IMPORT_COMPONENT -> { val path = intent.getStringExtra(EXTRA_FILE_PATH) ?: run { Log.w(BuildConfig.TAG, "Missing $EXTRA_FILE_PATH extra for intent $INTENT_IMPORT_COMPONENT") mainThread { showToast(R.string.intent_import_component_failure) } return@launchBlock } val componentType = intent.getStringExtra(EXTRA_COMPONENT_TYPE) ?: run { Log.w(BuildConfig.TAG, "Missing $EXTRA_COMPONENT_TYPE extra for intent $INTENT_IMPORT_COMPONENT") mainThread { showToast(R.string.intent_import_component_failure) } return@launchBlock } val file = File("/data/local/tmp", path) if (!file.exists()) { Log.w(BuildConfig.TAG, "Intent $INTENT_IMPORT_COMPONENT specified an invalid file!") mainThread { showToast(R.string.intent_import_component_failure) } return@launchBlock } val targetDir = when (componentType) { "tidal" -> paths.customTidalApksDir "patches" -> paths.customPatchesDir else -> { Log.w(BuildConfig.TAG, "Extra $EXTRA_COMPONENT_TYPE is not a valid value!") mainThread { showToast(R.string.intent_import_component_failure) } return@launchBlock } } try { file.copyTo(targetDir.resolve(file.name), overwrite = true) file.delete() // This most likely silently fails } catch (e: Exception) { Log.e(BuildConfig.TAG, "Failed to import custom component", e) mainThread { showToast(R.string.intent_import_component_failure) } } mainThread { showToast(R.string.intent_import_component_success, file.name) } } else -> { Log.w(BuildConfig.TAG, "Unhandled intent ${intent.action}") } } } private suspend fun handleReinstall(packageName: String): Screen { val metadata = try { val applicationInfo = packageManager.getApplicationInfo(packageName, 0) val metadataFile = ZipReader(applicationInfo.publicSourceDir) .use { it.openEntry("rlmobile.json")?.read() } metadataFile?.let { json.decodeFromStream(it.inputStream()) } } catch (t: Throwable) { Log.w(BuildConfig.TAG, "Failed to parse Radiant Lyrics install metadata from package $packageName", t) mainThread { showToast(R.string.intent_reinstall_fail) } null } val patchOptions = metadata?.options ?: PatchOptions.Default.copy(packageName = packageName) return PatchingScreen(patchOptions) } companion object { const val INTENT_REINSTALL = "com.meowarex.rlmobile.REINSTALL" const val INTENT_IMPORT_COMPONENT = "com.meowarex.rlmobile.IMPORT_COMPONENT" const val EXTRA_PACKAGE_NAME = "rlmobile.packageName" const val EXTRA_FILE_PATH = "rlmobile.file" const val EXTRA_COMPONENT_TYPE = "rlmobile.componentType" } }