compose: spoof: Migrate spoof menu to compose

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-12-08 15:07:02 +08:00
parent 28e93b0af7
commit 3c01dd28fa
6 changed files with 174 additions and 58 deletions

View File

@@ -62,6 +62,8 @@ object Constants {
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
const val JSON_MIME_TYPE = "application/json"
const val PROPERTIES_IMPORT_MIME_TYPE = "application/octet-stream"
const val PROPERTIES_EXPORT_MIME_TYPE = "text/x-java-properties"
// PACKAGE NAMES
const val PACKAGE_NAME_GMS = "com.google.android.gms"

View File

@@ -5,6 +5,10 @@
package com.aurora.store.compose.ui.spoof
import android.net.Uri
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,17 +31,29 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastForEachIndexed
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.aurora.Constants
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.compose.composable.TopAppBar
import com.aurora.store.compose.ui.spoof.menu.MenuItem
import com.aurora.store.compose.ui.spoof.menu.SpoofMenu
import com.aurora.store.compose.ui.spoof.navigation.SpoofPage
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.viewmodel.spoof.SpoofViewModel
import kotlinx.coroutines.launch
@Composable
fun SpoofScreen(onNavigateUp: () -> Unit, onNavigateToSplash: () -> Unit) {
fun SpoofScreen(
onNavigateUp: () -> Unit,
onNavigateToSplash: () -> Unit,
viewModel: SpoofViewModel = hiltViewModel(),
) {
ScreenContent(
onNavigateUp = onNavigateUp,
onNavigateToSplash = onNavigateToSplash
onNavigateToSplash = onNavigateToSplash,
onDeviceSpoofImport = { uri -> viewModel.importDeviceSpoof(uri) },
onDeviceSpoofExport = { uri -> viewModel.exportDeviceSpoof(uri) }
)
}
@@ -45,13 +61,36 @@ fun SpoofScreen(onNavigateUp: () -> Unit, onNavigateToSplash: () -> Unit) {
private fun ScreenContent(
pages: List<SpoofPage> = listOf(SpoofPage.DEVICE, SpoofPage.LOCALE),
onNavigateUp: () -> Unit = {},
onNavigateToSplash: () -> Unit = {}
onNavigateToSplash: () -> Unit = {},
onDeviceSpoofImport: (uri: Uri) -> Unit = {},
onDeviceSpoofExport: (uri: Uri) -> Unit = {}
) {
val context = LocalContext.current
val pagerState = rememberPagerState { pages.size }
val coroutineScope = rememberCoroutineScope()
val snackBarHostState = remember { SnackbarHostState() }
val docImportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = {
if (it != null) {
onDeviceSpoofImport(it)
} else {
context.toast(R.string.toast_import_failed)
}
}
)
val docExportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(Constants.PROPERTIES_EXPORT_MIME_TYPE),
onResult = {
if (it != null) {
onDeviceSpoofExport(it)
} else {
context.toast(R.string.toast_export_failed)
}
}
)
fun onRequestNavigateToSplash() {
coroutineScope.launch {
val result = snackBarHostState.showSnackbar(
@@ -64,11 +103,29 @@ private fun ScreenContent(
AccountProvider.logout(context)
onNavigateToSplash()
}
else -> Unit
}
}
}
@Composable
fun SetupMenu() {
SpoofMenu { menuItem ->
when (menuItem) {
MenuItem.IMPORT -> {
docImportLauncher.launch(arrayOf(Constants.PROPERTIES_IMPORT_MIME_TYPE))
}
MenuItem.EXPORT -> {
docExportLauncher.launch(
"aurora_store_${Build.BRAND}_${Build.DEVICE}.properties"
)
}
}
}
}
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackBarHostState)
@@ -76,7 +133,8 @@ private fun ScreenContent(
topBar = {
TopAppBar(
title = stringResource(R.string.title_spoof_manager),
onNavigateUp = onNavigateUp
onNavigateUp = onNavigateUp,
actions = { SetupMenu() }
)
}
) { paddingValues ->

View File

@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.spoof.menu
enum class MenuItem {
IMPORT,
EXPORT
}

View File

@@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.spoof.menu
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
import com.aurora.store.compose.preview.PreviewTemplate
/**
* Menu for the blacklist screen
* @param modifier The modifier to be applied to the composable
* @param onMenuItemClicked Callback when a menu item has been clicked
* @see MenuItem
*/
@Composable
fun SpoofMenu(
modifier: Modifier = Modifier,
isExpanded: Boolean = false,
onMenuItemClicked: (menuItem: MenuItem) -> Unit = {}
) {
var expanded by remember { mutableStateOf(isExpanded) }
fun onClick(menuItem: MenuItem) {
onMenuItemClicked(menuItem)
expanded = false
}
Box(modifier = modifier) {
IconButton(onClick = { expanded = true }) {
Icon(
painter = painterResource(R.drawable.ic_more_vert),
contentDescription = stringResource(R.string.menu)
)
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_import)) },
onClick = { onClick(MenuItem.IMPORT) }
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_export)) },
onClick = { onClick(MenuItem.EXPORT) }
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun SpoofMenuPreview() {
PreviewTemplate {
SpoofMenu(isExpanded = true)
}
}

View File

@@ -1,9 +1,12 @@
package com.aurora.store.viewmodel.spoof
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import com.aurora.store.data.providers.NativeDeviceInfoProvider
import com.aurora.store.data.providers.SpoofProvider
import com.aurora.store.util.PathUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
@@ -14,9 +17,11 @@ import javax.inject.Inject
@HiltViewModel
class SpoofViewModel @Inject constructor(
val spoofProvider: SpoofProvider,
private val spoofProvider: SpoofProvider,
@ApplicationContext private val context: Context
): ViewModel() {
) : ViewModel() {
private val TAG = SpoofViewModel::class.java.simpleName
val defaultLocale: Locale = Locale.getDefault()
val defaultProperties = NativeDeviceInfoProvider.getNativeDeviceProperties(context)
@@ -52,4 +57,26 @@ class SpoofViewModel @Inject constructor(
spoofProvider.setSpoofLocale(locale)
}
}
fun importDeviceSpoof(uri: Uri) {
try {
context.contentResolver?.openInputStream(uri)?.use { input ->
PathUtil.getNewEmptySpoofConfig(context).outputStream().use {
input.copyTo(it)
}
}
_availableDevices.value = spoofProvider.availableDeviceProperties
} catch (exception: Exception) {
Log.e(TAG, "Failed to import device config", exception)
}
}
fun exportDeviceSpoof(uri: Uri) {
try {
NativeDeviceInfoProvider.getNativeDeviceProperties(context, true)
.store(context.contentResolver?.openOutputStream(uri), "DEVICE_CONFIG")
} catch (exception: Exception) {
Log.e(TAG, "Failed to export device config", exception)
}
}
}

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Aurora Store
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:menu="@menu/menu_import_export"
app:navigationIcon="@drawable/ic_arrow_back"
app:title="@string/title_spoof_manager" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar"
android:background="@android:color/transparent"
app:tabGravity="fill"
app:tabIndicator="@drawable/tab_indicator"
app:tabIndicatorHeight="2dp"
app:tabMode="scrollable"
app:tabPaddingEnd="@dimen/padding_small"
app:tabPaddingStart="@dimen/padding_small" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tab_layout" />
</RelativeLayout>