From 3f497a93d060a37e744c473f2641f6487a9916c9 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Tue, 6 Aug 2024 13:23:27 +0200 Subject: [PATCH] UI refactoring/functionality WIP --- .../java/app/revanced/manager/MainActivity.kt | 10 +------- .../repository/DownloadedAppRepository.kt | 2 +- .../manager/ui/screen/AppSelectorScreen.kt | 13 +++++------ .../manager/ui/screen/PatcherScreen.kt | 14 ++++++----- .../ui/viewmodel/AppSelectorViewModel.kt | 14 +++++++++-- .../manager/ui/viewmodel/PatcherViewModel.kt | 23 +++++++++++-------- .../revanced/manager/util/IntentContract.kt | 12 ---------- .../java/app/revanced/manager/util/Util.kt | 18 ++++++++++++++- 8 files changed, 58 insertions(+), 48 deletions(-) delete mode 100644 app/src/main/java/app/revanced/manager/util/IntentContract.kt diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index 1003c19f..c3c6daf5 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -10,7 +10,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import app.revanced.manager.ui.destination.Destination import app.revanced.manager.ui.destination.SettingsDestination -import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.screen.AppSelectorScreen import app.revanced.manager.ui.screen.DashboardScreen import app.revanced.manager.ui.screen.InstalledAppInfoScreen @@ -99,14 +98,7 @@ class MainActivity : ComponentActivity() { is Destination.AppSelector -> AppSelectorScreen( // onAppClick = { navController.navigate(Destination.VersionSelector(it)) }, // TODO: complete this feature - onAppClick = { packageName, version -> - navController.navigate( - Destination.SelectedApplicationInfo( - SelectedApp.Search(packageName, version) - ) - ) - }, - onStorageClick = { + onSelect = { navController.navigate( Destination.SelectedApplicationInfo( it diff --git a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt index 124d5484..dc701cf4 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt @@ -17,7 +17,7 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) { fun getAll() = dao.getAllApps().distinctUntilChanged() - fun getApkFileForApp(app: DownloadedApp): File = getApkFileForDir(dir.resolve(app.directory)) + private fun getApkFileForApp(app: DownloadedApp): File = getApkFileForDir(dir.resolve(app.directory)) private fun getApkFileForDir(directory: File) = directory.listFiles()!!.first() suspend fun download( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt index c0ffcb55..0df083b9 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.material.icons.filled.Storage import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -33,19 +32,19 @@ import app.revanced.manager.ui.component.SearchView import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.util.APK_MIMETYPE +import app.revanced.manager.util.EventEffect import app.revanced.manager.util.transparentListItemColors import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun AppSelectorScreen( - onAppClick: (packageName: String, version: String?) -> Unit, - onStorageClick: (SelectedApp.Local) -> Unit, + onSelect: (SelectedApp) -> Unit, onBackClick: () -> Unit, vm: AppSelectorViewModel = koinViewModel() ) { - SideEffect { - vm.onStorageClick = onStorageClick + EventEffect(flow = vm.appSelectionFlow) { + onSelect(it) } val pickApkLauncher = @@ -90,7 +89,7 @@ fun AppSelectorScreen( ) { app -> ListItem( modifier = Modifier.clickable { - onAppClick( + vm.selectApp( app.packageName, suggestedVersions[app.packageName] ) @@ -190,7 +189,7 @@ fun AppSelectorScreen( ) { app -> ListItem( modifier = Modifier.clickable { - onAppClick( + vm.selectApp( app.packageName, suggestedVersions[app.packageName] ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 28816545..edd4f564 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -2,6 +2,7 @@ package app.revanced.manager.ui.screen import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement @@ -49,7 +50,7 @@ import app.revanced.manager.ui.model.State import app.revanced.manager.ui.model.StepCategory import app.revanced.manager.ui.viewmodel.PatcherViewModel import app.revanced.manager.util.APK_MIMETYPE -import app.revanced.manager.util.IntentContract +import app.revanced.manager.util.EventEffect @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -95,11 +96,12 @@ fun PatcherScreen( onConfirm = vm::install ) - val activityLauncher = rememberLauncherForActivityResult(contract = IntentContract) { - vm.handleActivityResult(it) - } - SideEffect { - vm.launchActivity = activityLauncher::launch + val activityLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + onResult = vm::handleActivityResult + ) + EventEffect(flow = vm.launchActivityFlow) { intent -> + activityLauncher.launch(intent) } if (vm.activeInteractionRequest) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt index a2174852..3f810f63 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt @@ -14,7 +14,11 @@ import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.util.PM import app.revanced.manager.util.toast import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -30,13 +34,19 @@ class AppSelectorViewModel( } val appList = pm.appList - var onStorageClick: (SelectedApp.Local) -> Unit = {} + private val appSelectionChannel = Channel() + val appSelectionFlow = appSelectionChannel.receiveAsFlow() val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default) var nonSuggestedVersionDialogSubject by mutableStateOf(null) private set + private suspend fun selectApp(app: SelectedApp) = appSelectionChannel.send(app) + fun selectApp(packageName: String, version: String?) = viewModelScope.launch { + selectApp(SelectedApp.Search(packageName, version)) + } + fun loadLabel(app: PackageInfo?) = with(pm) { app?.label() ?: "Not installed" } fun dismissNonSuggestedVersionDialog() { @@ -54,7 +64,7 @@ class AppSelectorViewModel( } if (patchBundleRepository.isVersionAllowed(selectedApp.packageName, selectedApp.version)) { - onStorageClick(selectedApp) + selectApp(selectedApp) } else { nonSuggestedVersionDialogSubject = selectedApp } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index 695716c2..83adb9b4 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -9,6 +9,7 @@ import android.content.IntentFilter import android.content.pm.PackageInstaller import android.net.Uri import android.util.Log +import androidx.activity.result.ActivityResult import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -39,7 +40,6 @@ import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.State import app.revanced.manager.ui.model.Step import app.revanced.manager.ui.model.StepCategory -import app.revanced.manager.util.IntentContract import app.revanced.manager.util.PM import app.revanced.manager.util.simpleMessage import app.revanced.manager.util.tag @@ -49,8 +49,10 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.time.withTimeout import kotlinx.coroutines.withContext @@ -83,8 +85,9 @@ class PatcherViewModel( null ) val activeInteractionRequest by derivedStateOf { currentInteractionRequest != null } - private var launchedActivity: CompletableDeferred? = null - var launchActivity: (Intent) -> Unit = {} + private var launchedActivity: CompletableDeferred? = null + private val launchActivityChannel = Channel() + val launchActivityFlow = launchActivityChannel.receiveAsFlow() private val tempDir = fs.tempDir.resolve("installer").also { it.deleteRecursively() @@ -249,17 +252,17 @@ class PatcherViewModel( withContext(Dispatchers.Main) { if (launchedActivity != null) throw Exception("An activity has already been launched.") try { - val job = CompletableDeferred() - launchActivity(intent) + val job = CompletableDeferred() + launchActivityChannel.send(intent) launchedActivity = job val result = job.await() - when (result.code) { - Activity.RESULT_OK -> result.intent + when (result.resultCode) { + Activity.RESULT_OK -> result.data Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled() else -> throw UserInteractionException.Activity.NotCompleted( - result.code, - result.intent + result.resultCode, + result.data ) } } finally { @@ -269,7 +272,7 @@ class PatcherViewModel( }) } - fun handleActivityResult(result: IntentContract.Result) { + fun handleActivityResult(result: ActivityResult) { launchedActivity?.complete(result) } diff --git a/app/src/main/java/app/revanced/manager/util/IntentContract.kt b/app/src/main/java/app/revanced/manager/util/IntentContract.kt deleted file mode 100644 index 5716e051..00000000 --- a/app/src/main/java/app/revanced/manager/util/IntentContract.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.manager.util - -import android.content.Context -import android.content.Intent -import androidx.activity.result.contract.ActivityResultContract - -object IntentContract : ActivityResultContract() { - override fun createIntent(context: Context, input: Intent) = input - override fun parseResult(resultCode: Int, intent: Intent?) = Result(resultCode, intent) - - class Result(val code: Int, val intent: Intent?) -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index 494d4da5..b1d83d3a 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -15,18 +15,20 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.ListItemColors import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import app.revanced.manager.R @@ -201,6 +203,20 @@ val transparentListItemColors ?: ListItemDefaults.colors(containerColor = Color.Transparent) .also { transparentListItemColorsCached = it } +@Composable +fun EventEffect(flow: Flow, vararg keys: Any?, state: Lifecycle.State = Lifecycle.State.STARTED, block: suspend (T) -> Unit) { + val lifecycleOwner = LocalLifecycleOwner.current + val currentBlock by rememberUpdatedState(block) + + LaunchedEffect(flow, state, *keys) { + lifecycleOwner.repeatOnLifecycle(state) { + flow.collect { + currentBlock(it) + } + } + } +} + const val isScrollingUpSensitivity = 10 @Composable