From 54f0a69596ce80566e156f7162906932a2944fd2 Mon Sep 17 00:00:00 2001 From: CnC-Robert Date: Sat, 6 May 2023 12:42:30 +0200 Subject: [PATCH] feat: app selector screen --- app/build.gradle.kts | 23 +-- app/src/main/AndroidManifest.xml | 4 +- .../revanced/manager/compose/MainActivity.kt | 30 ++- .../manager/compose/installer/utils/PM.kt | 68 ------- .../{installer => }/service/InstallService.kt | 2 +- .../service/UninstallService.kt | 2 +- .../manager/compose/ui/component/AppIcon.kt | 41 ++++ .../compose/ui/component/AppScaffold.kt | 60 ++++++ .../compose/ui/component/LoadingIndicator.kt | 31 +++ .../destination/Destination.kt} | 5 +- .../compose/ui/screen/AppSelectorScreen.kt | 178 ++++++++++++++++++ .../compose/ui/screen/DashboardScreen.kt | 27 ++- .../compose/ui/screen/InstalledAppsScreen.kt | 8 +- .../compose/ui/screen/SourcesScreen.kt | 8 +- .../manager/compose/ui/theme/Theme.kt | 20 +- .../app/revanced/manager/compose/util/PM.kt | 161 ++++++++++++++++ app/src/main/res/values/strings.xml | 20 ++ 17 files changed, 575 insertions(+), 113 deletions(-) delete mode 100644 app/src/main/java/app/revanced/manager/compose/installer/utils/PM.kt rename app/src/main/java/app/revanced/manager/compose/{installer => }/service/InstallService.kt (96%) rename app/src/main/java/app/revanced/manager/compose/{installer => }/service/UninstallService.kt (95%) create mode 100644 app/src/main/java/app/revanced/manager/compose/ui/component/AppIcon.kt create mode 100644 app/src/main/java/app/revanced/manager/compose/ui/component/AppScaffold.kt create mode 100644 app/src/main/java/app/revanced/manager/compose/ui/component/LoadingIndicator.kt rename app/src/main/java/app/revanced/manager/compose/{destination/AppDestination.kt => ui/destination/Destination.kt} (60%) create mode 100644 app/src/main/java/app/revanced/manager/compose/ui/screen/AppSelectorScreen.kt create mode 100644 app/src/main/java/app/revanced/manager/compose/util/PM.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 61b779fe..1afd909a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,29 +46,29 @@ dependencies { implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.activity:activity-compose:1.7.1") + implementation("androidx.paging:paging-common-ktx:3.1.1") // Compose - implementation(platform("androidx.compose:compose-bom:2023.04.01")) + implementation(platform("androidx.compose:compose-bom:2023.05.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.paging:paging-common-ktx:3.1.1") - implementation("androidx.core:core-ktx:1.10.0") + implementation("androidx.compose.material:material-icons-extended") + implementation("androidx.compose.material3:material3:1.1.0-rc01") // Accompanist - val accompanistVersion = "0.30.1" - implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion") + //val accompanistVersion = "0.30.1" + //implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion") //implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion") - implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion") + //implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion") //implementation("com.google.accompanist:accompanist-flowlayout:$accompanistVersion") //implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion") + // Coil (async image loading, network image) + implementation("io.coil-kt:coil-compose:2.2.2") + // KotlinX implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") - // Material 3 - implementation("androidx.compose.material3:material3") - - // ReVanced implementation("app.revanced:revanced-patcher:7.0.0") @@ -86,4 +86,5 @@ dependencies { implementation("io.ktor:ktor-client-okhttp:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") -} + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af437b32..4928e873 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,7 @@ - - + + \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/MainActivity.kt b/app/src/main/java/app/revanced/manager/compose/MainActivity.kt index 25852407..c07be1f6 100644 --- a/app/src/main/java/app/revanced/manager/compose/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/compose/MainActivity.kt @@ -6,24 +6,38 @@ import androidx.activity.compose.setContent import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.isSystemInDarkTheme import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import app.revanced.manager.compose.destination.Destination import app.revanced.manager.compose.domain.manager.PreferencesManager +import app.revanced.manager.compose.ui.destination.Destination +import app.revanced.manager.compose.ui.screen.AppSelectorScreen import app.revanced.manager.compose.ui.screen.DashboardScreen import app.revanced.manager.compose.ui.theme.ReVancedManagerTheme import app.revanced.manager.compose.ui.theme.Theme +import app.revanced.manager.compose.util.PM import dev.olshevski.navigation.reimagined.AnimatedNavHost import dev.olshevski.navigation.reimagined.NavBackHandler +import dev.olshevski.navigation.reimagined.navigate +import dev.olshevski.navigation.reimagined.pop import dev.olshevski.navigation.reimagined.rememberNavController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import org.koin.android.ext.android.inject class MainActivity : ComponentActivity() { private val prefs: PreferencesManager by inject() + private val mainScope = MainScope() @ExperimentalAnimationApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) installSplashScreen() + + val context = this + mainScope.launch(Dispatchers.IO) { + PM.loadApps(context) + } + setContent { ReVancedManagerTheme( darkTheme = prefs.theme == Theme.SYSTEM && isSystemInDarkTheme() || prefs.theme == Theme.DARK, @@ -34,12 +48,18 @@ class MainActivity : ComponentActivity() { NavBackHandler(navController) AnimatedNavHost( - controller = navController, + controller = navController ) { destination -> when (destination) { - is Destination.Dashboard -> { - DashboardScreen() - } + + is Destination.Dashboard -> { DashboardScreen( + onAppSelectorClick = { navController.navigate(Destination.AppSelector) } + ) } + + is Destination.AppSelector -> AppSelectorScreen( + onBackClick = { navController.pop() } + ) + } } } diff --git a/app/src/main/java/app/revanced/manager/compose/installer/utils/PM.kt b/app/src/main/java/app/revanced/manager/compose/installer/utils/PM.kt deleted file mode 100644 index aa84e45b..00000000 --- a/app/src/main/java/app/revanced/manager/compose/installer/utils/PM.kt +++ /dev/null @@ -1,68 +0,0 @@ -package app.revanced.manager.compose.installer.utils - -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller -import android.content.pm.PackageManager -import android.os.Build -import app.revanced.manager.compose.installer.service.InstallService -import app.revanced.manager.compose.installer.service.UninstallService -import java.io.File - -private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable - -object PM { - - fun installApp(apk: File, context: Context) { - val packageInstaller = context.packageManager.packageInstaller - val session = - packageInstaller.openSession(packageInstaller.createSession(sessionParams)) - session.writeApk(apk) - session.commit(context.installIntentSender) - session.close() - } - - fun uninstallPackage(pkg: String, context: Context) { - val packageInstaller = context.packageManager.packageInstaller - packageInstaller.uninstall(pkg, context.uninstallIntentSender) - } -} - -private fun PackageInstaller.Session.writeApk(apk: File) { - apk.inputStream().use { inputStream -> - openWrite(apk.name, 0, apk.length()).use { outputStream -> - inputStream.copyTo(outputStream, byteArraySize) - fsync(outputStream) - } - } -} - -private val intentFlags - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - PendingIntent.FLAG_MUTABLE - else - 0 - -private val sessionParams - get() = PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL - ).apply { - setInstallReason(PackageManager.INSTALL_REASON_USER) - } - -private val Context.installIntentSender - get() = PendingIntent.getService( - this, - 0, - Intent(this, InstallService::class.java), - intentFlags - ).intentSender - -private val Context.uninstallIntentSender - get() = PendingIntent.getService( - this, - 0, - Intent(this, UninstallService::class.java), - intentFlags - ).intentSender \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/installer/service/InstallService.kt b/app/src/main/java/app/revanced/manager/compose/service/InstallService.kt similarity index 96% rename from app/src/main/java/app/revanced/manager/compose/installer/service/InstallService.kt rename to app/src/main/java/app/revanced/manager/compose/service/InstallService.kt index 2dea3242..cef0d7f4 100644 --- a/app/src/main/java/app/revanced/manager/compose/installer/service/InstallService.kt +++ b/app/src/main/java/app/revanced/manager/compose/service/InstallService.kt @@ -1,4 +1,4 @@ -package app.revanced.manager.compose.installer.service +package app.revanced.manager.compose.service import android.app.Service import android.content.Intent diff --git a/app/src/main/java/app/revanced/manager/compose/installer/service/UninstallService.kt b/app/src/main/java/app/revanced/manager/compose/service/UninstallService.kt similarity index 95% rename from app/src/main/java/app/revanced/manager/compose/installer/service/UninstallService.kt rename to app/src/main/java/app/revanced/manager/compose/service/UninstallService.kt index 5780160b..492eda3b 100644 --- a/app/src/main/java/app/revanced/manager/compose/installer/service/UninstallService.kt +++ b/app/src/main/java/app/revanced/manager/compose/service/UninstallService.kt @@ -1,4 +1,4 @@ -package app.revanced.manager.compose.installer.service +package app.revanced.manager.compose.service import android.app.Service import android.content.Intent diff --git a/app/src/main/java/app/revanced/manager/compose/ui/component/AppIcon.kt b/app/src/main/java/app/revanced/manager/compose/ui/component/AppIcon.kt new file mode 100644 index 00000000..8d8bdfdf --- /dev/null +++ b/app/src/main/java/app/revanced/manager/compose/ui/component/AppIcon.kt @@ -0,0 +1,41 @@ +package app.revanced.manager.compose.ui.component + +import android.graphics.drawable.Drawable +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Android +import androidx.compose.material3.LocalContentColor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import coil.compose.rememberAsyncImagePainter + +@Composable +fun AppIcon( + drawable: Drawable?, + contentDescription: String?, + size: Int = 48 +) { + if (drawable == null) { + val image = rememberVectorPainter(Icons.Default.Android) + val colorFilter = ColorFilter.tint(LocalContentColor.current) + + Image( + image, + contentDescription, + Modifier.size(size.dp), + colorFilter = colorFilter + ) + } else { + val image = rememberAsyncImagePainter(drawable) + + Image( + image, + contentDescription, + Modifier.size(size.dp) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/component/AppScaffold.kt b/app/src/main/java/app/revanced/manager/compose/ui/component/AppScaffold.kt new file mode 100644 index 00000000..0ac8a095 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/compose/ui/component/AppScaffold.kt @@ -0,0 +1,60 @@ +package app.revanced.manager.compose.ui.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppScaffold( + topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, + bottomBar: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + content: @Composable (PaddingValues) -> Unit +) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { topBar(scrollBehavior) }, + bottomBar = bottomBar, + floatingActionButton = floatingActionButton, + content = content + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppTopBar( + title: String, + onBackClick: (() -> Unit)? = null, + actions: @Composable (RowScope.() -> Unit) = {}, + scrollBehavior: TopAppBarScrollBehavior? = null +) { + val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) + + TopAppBar( + title = { Text(title) }, + scrollBehavior = scrollBehavior, + navigationIcon = { + if (onBackClick != null) { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = null + ) + } + } + }, + actions = actions, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = containerColor + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/component/LoadingIndicator.kt b/app/src/main/java/app/revanced/manager/compose/ui/component/LoadingIndicator.kt new file mode 100644 index 00000000..398dd330 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/compose/ui/component/LoadingIndicator.kt @@ -0,0 +1,31 @@ +package app.revanced.manager.compose.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.revanced.manager.compose.R + +@Composable +fun LoadingIndicator(progress: Float? = null, text: Int? = R.string.loading_body) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (text != null) + Text(stringResource(text)) + if (progress == null) { + CircularProgressIndicator(modifier = Modifier.padding(vertical = 16.dp)) + } else { + CircularProgressIndicator(progress = progress, modifier = Modifier.padding(vertical = 16.dp)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/destination/AppDestination.kt b/app/src/main/java/app/revanced/manager/compose/ui/destination/Destination.kt similarity index 60% rename from app/src/main/java/app/revanced/manager/compose/destination/AppDestination.kt rename to app/src/main/java/app/revanced/manager/compose/ui/destination/Destination.kt index 22de2271..534709b0 100644 --- a/app/src/main/java/app/revanced/manager/compose/destination/AppDestination.kt +++ b/app/src/main/java/app/revanced/manager/compose/ui/destination/Destination.kt @@ -1,4 +1,4 @@ -package app.revanced.manager.compose.destination +package app.revanced.manager.compose.ui.destination import android.os.Parcelable import kotlinx.parcelize.Parcelize @@ -8,4 +8,7 @@ sealed interface Destination: Parcelable { @Parcelize object Dashboard: Destination + @Parcelize + object AppSelector: Destination + } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/screen/AppSelectorScreen.kt b/app/src/main/java/app/revanced/manager/compose/ui/screen/AppSelectorScreen.kt new file mode 100644 index 00000000..25fd50b2 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/compose/ui/screen/AppSelectorScreen.kt @@ -0,0 +1,178 @@ +package app.revanced.manager.compose.ui.screen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.HelpOutline +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.revanced.manager.compose.R +import app.revanced.manager.compose.ui.component.AppIcon +import app.revanced.manager.compose.ui.component.AppScaffold +import app.revanced.manager.compose.ui.component.AppTopBar +import app.revanced.manager.compose.ui.component.LoadingIndicator +import app.revanced.manager.compose.util.PM + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppSelectorScreen( + onBackClick: () -> Unit +) { + var filterText by rememberSaveable { mutableStateOf("") } + var search by rememberSaveable { mutableStateOf(false) } + + // TODO: find something better for this + if (search) { + SearchBar( + query = filterText, + onQueryChange = { filterText = it }, + onSearch = { }, + active = true, + onActiveChange = { search = it }, + modifier = Modifier.fillMaxSize(), + placeholder = { Text(stringResource(R.string.search_apps)) }, + leadingIcon = { IconButton({ search = false }) { Icon(Icons.Default.ArrowBack, null) } }, + shape = SearchBarDefaults.inputFieldShape, + content = { + if (PM.appList.isNotEmpty()) { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items( + PM.appList + .filter { app -> + (app.label.contains( + filterText, + true + ) or app.packageName.contains(filterText, true)) + } + ) { app -> + + ListItem( + modifier = Modifier.clickable { }, + leadingContent = { AppIcon(app.icon, null, 36) }, + headlineContent = { Text(app.label) }, + supportingContent = { Text(app.packageName) }, + trailingContent = { Text((PM.testList[app.packageName]?: 0).let { if (it == 1) "$it Patch" else "$it Patches" }) } + ) + + } + } + } else { + LoadingIndicator() + } + } + ) + } + + AppScaffold( + topBar = { + AppTopBar( + title = "Select an app", + onBackClick = onBackClick, + actions = { + IconButton({}) { + Icon(Icons.Outlined.HelpOutline, "Help") + } + IconButton(onClick = { search = true }) { + Icon(Icons.Outlined.Search, "Search") + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + if (PM.supportedAppList.isNotEmpty()) { + + /*Row( + modifier = Modifier.horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + FilterChip( + selected = false, + onClick = {}, + label = { Text("Patched apps") }, + leadingIcon = { Icon(Icons.Default.Check, null) }, + enabled = false + ) + FilterChip( + selected = false, + onClick = {}, + label = { Text("User apps") }, + leadingIcon = { Icon(Icons.Default.Android, null) } + ) + FilterChip( + selected = filterSystemApps, + onClick = { filterSystemApps = !filterSystemApps }, + label = { Text("System apps") }, + leadingIcon = { Icon(Icons.Default.Apps, null) } + ) + }*/ + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { + + ListItem( + modifier = Modifier.clickable { }, + leadingContent = { Box(Modifier.size(36.dp), Alignment.Center) { Icon(Icons.Default.Storage, null, modifier = Modifier.size(24.dp)) } }, + headlineContent = { Text("Select from storage") } + ) + + Divider() + + } + + (PM.appList.ifEmpty { PM.supportedAppList }).also { list -> + items( + count = list.size, + key = { list[it].packageName } + ) { index -> + val app = list[index] + + ListItem( + modifier = Modifier.clickable { }, + leadingContent = { AppIcon(app.icon, null, 36) }, + headlineContent = { Text(app.label) }, + supportingContent = { Text(app.packageName) }, + trailingContent = { + Text( + (PM.testList[app.packageName]?: 0).let { if (it == 1) "$it Patch" else "$it Patches" } + ) + } + ) + + } + + if (PM.appList.isEmpty()) { + item { + Box(Modifier.fillMaxWidth(), Alignment.Center) { + CircularProgressIndicator(Modifier.padding(vertical = 15.dp).size(24.dp), strokeWidth = 3.dp) + } + } + } + } + } + } else { + LoadingIndicator() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/compose/ui/screen/DashboardScreen.kt index bca37a17..c403c651 100644 --- a/app/src/main/java/app/revanced/manager/compose/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/compose/ui/screen/DashboardScreen.kt @@ -15,16 +15,18 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import app.revanced.manager.compose.R +import app.revanced.manager.compose.ui.component.AppScaffold +import app.revanced.manager.compose.ui.component.AppTopBar import kotlinx.coroutines.launch enum class DashboardPage( @@ -37,16 +39,18 @@ enum class DashboardPage( @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable -fun DashboardScreen() { +fun DashboardScreen( + onAppSelectorClick: () -> Unit +) { val pages: Array = DashboardPage.values() val pagerState = rememberPagerState() val coroutineScope = rememberCoroutineScope() - Scaffold( + AppScaffold( topBar = { - TopAppBar( - title = { Text("ReVanced Manager") }, + AppTopBar( + title = "ReVanced Manager", actions = { IconButton(onClick = {}) { Icon(imageVector = Icons.Outlined.Info, contentDescription = null) @@ -61,13 +65,20 @@ fun DashboardScreen() { ) }, floatingActionButton = { - FloatingActionButton(onClick = {}) { + FloatingActionButton(onClick = { + if (pagerState.currentPage == DashboardPage.DASHBOARD.ordinal) + onAppSelectorClick() + } + ) { Icon(imageVector = Icons.Default.Add, contentDescription = null) } } ) { paddingValues -> Column(Modifier.padding(paddingValues)) { - TabRow(selectedTabIndex = pagerState.currentPage) { + TabRow( + selectedTabIndex = pagerState.currentPage, + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) + ) { pages.forEachIndexed { index, page -> val title = stringResource(id = page.titleResId) Tab( diff --git a/app/src/main/java/app/revanced/manager/compose/ui/screen/InstalledAppsScreen.kt b/app/src/main/java/app/revanced/manager/compose/ui/screen/InstalledAppsScreen.kt index 6774c066..412b1151 100644 --- a/app/src/main/java/app/revanced/manager/compose/ui/screen/InstalledAppsScreen.kt +++ b/app/src/main/java/app/revanced/manager/compose/ui/screen/InstalledAppsScreen.kt @@ -2,22 +2,20 @@ package app.revanced.manager.compose.ui.screen import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.sp import app.revanced.manager.compose.R @Composable fun InstalledAppsScreen() { - Box(Modifier.fillMaxSize()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text( text = stringResource(R.string.no_patched_apps_found), - fontSize = 24.sp, - modifier = Modifier - .align(alignment = Alignment.Center) + style = MaterialTheme.typography.titleLarge ) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/screen/SourcesScreen.kt b/app/src/main/java/app/revanced/manager/compose/ui/screen/SourcesScreen.kt index 3d9f1e0a..170e2fee 100644 --- a/app/src/main/java/app/revanced/manager/compose/ui/screen/SourcesScreen.kt +++ b/app/src/main/java/app/revanced/manager/compose/ui/screen/SourcesScreen.kt @@ -2,22 +2,20 @@ package app.revanced.manager.compose.ui.screen import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.sp import app.revanced.manager.compose.R @Composable fun SourcesScreen() { - Box(Modifier.fillMaxSize()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text( text = stringResource(R.string.no_sources_set), - fontSize = 24.sp, - modifier = Modifier - .align(alignment = Alignment.Center) + style = MaterialTheme.typography.titleLarge ) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/compose/ui/theme/Theme.kt b/app/src/main/java/app/revanced/manager/compose/ui/theme/Theme.kt index 3e871786..5711507c 100644 --- a/app/src/main/java/app/revanced/manager/compose/ui/theme/Theme.kt +++ b/app/src/main/java/app/revanced/manager/compose/ui/theme/Theme.kt @@ -2,14 +2,14 @@ package app.revanced.manager.compose.ui.theme import android.app.Activity import android.os.Build -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView -import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat private val DarkColorScheme = darkColorScheme( primary = Purple80, @@ -25,8 +25,8 @@ private val LightColorScheme = lightColorScheme( @Composable fun ReVancedManagerTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - dynamicColor: Boolean = true, + darkTheme: Boolean, + dynamicColor: Boolean, content: @Composable () -> Unit ) { val colorScheme = when { @@ -37,11 +37,19 @@ fun ReVancedManagerTheme( darkTheme -> DarkColorScheme else -> LightColorScheme } + val view = LocalView.current if (!view.isInEditMode) { SideEffect { - (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb() - ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme + val activity = view.context as Activity + + WindowCompat.setDecorFitsSystemWindows(activity.window, false) + + activity.window.navigationBarColor = colorScheme.background.toArgb() + activity.window.statusBarColor = Color.Transparent.toArgb() + + WindowCompat.getInsetsController(activity.window, view).isAppearanceLightStatusBars = !darkTheme + WindowCompat.getInsetsController(activity.window, view).isAppearanceLightNavigationBars = !darkTheme } } diff --git a/app/src/main/java/app/revanced/manager/compose/util/PM.kt b/app/src/main/java/app/revanced/manager/compose/util/PM.kt new file mode 100644 index 00000000..a6e11e01 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/compose/util/PM.kt @@ -0,0 +1,161 @@ +package app.revanced.manager.compose.util + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Parcelable +import androidx.compose.runtime.mutableStateListOf +import app.revanced.manager.compose.service.InstallService +import app.revanced.manager.compose.service.UninstallService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue +import java.io.File + +private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable + +@SuppressLint("QueryPermissionsNeeded") +@Suppress("DEPRECATION") +object PM { + + val testList = mapOf( + "com.google.android.youtube" to 59, + "com.android.vending" to 34, + "com.backdrops.wallpapers" to 2, + "com.termux" to 2, + "com.notinstalled.app" to 1, + "com.2notinstalled.app" to 1, + "org.adaway" to 5, + "com.activitymanager" to 1, + "com.guoshi.httpcanary" to 1, + "org.lsposed.lspatch" to 1, + "app.revanced.manager.flutter" to 100, + "com.reddit.frontpage" to 20 + ) + + val appList = mutableStateListOf() + val supportedAppList = mutableStateListOf() + + suspend fun loadApps(context: Context) { + val packageManager = context.packageManager + + testList.keys.map { + try { + val applicationInfo = packageManager.getApplicationInfo(it, 0) + + AppInfo( + it, + applicationInfo.loadLabel(packageManager).toString(), + applicationInfo.loadIcon(packageManager), + ) + } catch (e: PackageManager.NameNotFoundException) { + AppInfo( + it, + "Not installed" + ) + } + }.let { list -> + list.sortedWith( + compareByDescending { + testList[it.packageName] + }.thenBy { it.label }.thenBy { it.packageName } + ) + }.also { + withContext(Dispatchers.Main) { supportedAppList.addAll(it) } + } + + val localAppList = mutableListOf() + + packageManager.getInstalledApplications(PackageManager.GET_META_DATA).map { + AppInfo( + it.packageName, + it.loadLabel(packageManager).toString(), + it.loadIcon(packageManager) + ) + }.also { localAppList.addAll(it) } + + testList.keys.mapNotNull { packageName -> + if (!localAppList.any { packageName == it.packageName }) { + AppInfo( + packageName, + "Not installed" + ) + } else { + null + } + }.also { localAppList.addAll(it) } + + localAppList.sortWith( + compareByDescending { + testList[it.packageName] + }.thenBy { it.label }.thenBy { it.packageName } + ).also { + withContext(Dispatchers.Main) { appList.addAll(localAppList) } + } + } + + @Parcelize + data class AppInfo( + val packageName: String, + val label: String, + val icon: @RawValue Drawable? = null + ) : Parcelable + + fun installApp(apk: File, context: Context) { + val packageInstaller = context.packageManager.packageInstaller + val session = + packageInstaller.openSession(packageInstaller.createSession(sessionParams)) + session.writeApk(apk) + session.commit(context.installIntentSender) + session.close() + } + + fun uninstallPackage(pkg: String, context: Context) { + val packageInstaller = context.packageManager.packageInstaller + packageInstaller.uninstall(pkg, context.uninstallIntentSender) + } +} + +private fun PackageInstaller.Session.writeApk(apk: File) { + apk.inputStream().use { inputStream -> + openWrite(apk.name, 0, apk.length()).use { outputStream -> + inputStream.copyTo(outputStream, byteArraySize) + fsync(outputStream) + } + } +} + +private val intentFlags + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + PendingIntent.FLAG_MUTABLE + else + 0 + +private val sessionParams + get() = PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL + ).apply { + setInstallReason(PackageManager.INSTALL_REASON_USER) + } + +private val Context.installIntentSender + get() = PendingIntent.getService( + this, + 0, + Intent(this, InstallService::class.java), + intentFlags + ).intentSender + +private val Context.uninstallIntentSender + get() = PendingIntent.getService( + this, + 0, + Intent(this, UninstallService::class.java), + intentFlags + ).intentSender \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07b500f8..62ddf05c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,26 @@ ReVanced Manager + Patcher + Patches + Integrations + CLI + Manager + Dashboard + Settings + About + + Search apps… + Loading… + + Downloading patch bundle… + Downloading Integrations… + + Contributors + Appearance + Dynamic color + Theme + Storage Apps Sources No sources set