diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 61b779febc..1afd909a16 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 af437b3262..4928e87335 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 258524074d..c07be1f6b5 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 aa84e45b89..0000000000
--- 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 2dea32424c..cef0d7f4ab 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 5780160b1b..492eda3b3c 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 0000000000..8d8bdfdf8b
--- /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 0000000000..0ac8a095a7
--- /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 0000000000..398dd330c4
--- /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 22de22711c..534709b0b3 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 0000000000..25fd50b2aa
--- /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 bca37a173d..c403c651ec 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 6774c06678..412b115121 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 3d9f1e0a99..170e2feece 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 3e8717869f..5711507c0b 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 0000000000..a6e11e0140
--- /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 07b500f8ce..62ddf05cac 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