compose: add NetworkSheet, MoreSheet and AppMenuSheet
NetworkSheet replaces the legacy NetworkDialogSheet with a persistent modal that reacts to NetworkProvider status. MoreSheet hosts the account / settings / about entry points. AppMenuSheet provides the per-app long-click menu (blacklist, uninstall, export, info).
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.commons
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Destination
|
||||
import com.aurora.store.viewmodel.commons.MoreViewModel
|
||||
|
||||
private data class MoreItem(
|
||||
@StringRes val titleRes: Int,
|
||||
@DrawableRes val iconRes: Int,
|
||||
val onClick: () -> Unit
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MoreSheet(
|
||||
onDismiss: () -> Unit,
|
||||
onNavigateTo: (Destination) -> Unit = {},
|
||||
viewModel: MoreViewModel = hiltViewModel()
|
||||
) {
|
||||
val mainItems = listOf(
|
||||
MoreItem(R.string.title_apps_games, R.drawable.ic_apps) {
|
||||
onNavigateTo(Destination.Installed)
|
||||
},
|
||||
MoreItem(R.string.title_blacklist_manager, R.drawable.ic_blacklist) {
|
||||
onNavigateTo(Destination.Blacklist)
|
||||
},
|
||||
MoreItem(R.string.title_favourites_manager, R.drawable.ic_favorite_unchecked) {
|
||||
onNavigateTo(Destination.Favourite)
|
||||
},
|
||||
MoreItem(R.string.title_spoof_manager, R.drawable.ic_spoof) {
|
||||
onNavigateTo(Destination.Spoof)
|
||||
}
|
||||
)
|
||||
val extraItems = listOf(
|
||||
MoreItem(R.string.title_settings, R.drawable.ic_menu_settings) {
|
||||
onNavigateTo(Destination.Settings)
|
||||
},
|
||||
MoreItem(R.string.title_about, R.drawable.ic_menu_about) { onNavigateTo(Destination.About) }
|
||||
)
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
AccountHeader(
|
||||
viewModel = viewModel,
|
||||
onNavigateToAccounts = {
|
||||
onNavigateTo(Destination.Accounts)
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Column(
|
||||
Modifier.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_small),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
)
|
||||
) {
|
||||
mainItems.forEach { item ->
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(item.iconRes),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(item.titleRes)) },
|
||||
selected = false,
|
||||
onClick = {
|
||||
item.onClick()
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Column(
|
||||
Modifier.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_small),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
)
|
||||
) {
|
||||
extraItems.forEach { item ->
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(item.iconRes),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(item.titleRes)) },
|
||||
selected = false,
|
||||
onClick = {
|
||||
item.onClick()
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountHeader(viewModel: MoreViewModel, onNavigateToAccounts: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val isAnonymous = viewModel.authProvider.isAnonymous
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_large)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(viewModel.authProvider.authData?.userProfile?.artwork?.url)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_account),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(40.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = dimensionResource(R.dimen.padding_normal))
|
||||
) {
|
||||
Text(
|
||||
text = if (isAnonymous) {
|
||||
stringResource(R.string.account_anonymous)
|
||||
} else {
|
||||
viewModel.authProvider.authData?.userProfile?.name
|
||||
?: stringResource(R.string.status_unavailable)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = viewModel.authProvider.authData?.userProfile?.email
|
||||
?: stringResource(R.string.status_unavailable),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onNavigateToAccounts) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_account_manager),
|
||||
contentDescription = stringResource(R.string.manage_account)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.commons
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aurora.extensions.isQAndAbove
|
||||
import com.aurora.store.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NetworkSheet() {
|
||||
val context = LocalContext.current
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = {},
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
confirmValueChange = { it != SheetValue.Hidden }
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = dimensionResource(R.dimen.padding_xlarge))
|
||||
.padding(bottom = dimensionResource(R.dimen.padding_large)),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_network),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.requiredSize(48.dp)
|
||||
)
|
||||
Spacer(Modifier.padding(top = dimensionResource(R.dimen.padding_large)))
|
||||
Text(
|
||||
text = stringResource(R.string.title_no_network),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
Spacer(Modifier.padding(top = dimensionResource(R.dimen.padding_small)))
|
||||
Text(
|
||||
text = stringResource(R.string.check_connectivity),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.padding(top = dimensionResource(R.dimen.padding_xlarge)))
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
try {
|
||||
if (isQAndAbove) {
|
||||
context.startActivity(
|
||||
Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
|
||||
)
|
||||
} else {
|
||||
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
|
||||
}
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.i("NetworkSheet", "Unable to launch network settings")
|
||||
try {
|
||||
context.startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.action_check))
|
||||
}
|
||||
Spacer(Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.sheets
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import com.aurora.extensions.openInfo
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.viewmodel.sheets.AppMenuViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppMenuSheet(
|
||||
app: MinimalApp,
|
||||
onDismiss: () -> Unit,
|
||||
viewModel: AppMenuViewModel = hiltViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val isBlacklisted = viewModel.blacklistProvider.isBlacklisted(app.packageName)
|
||||
val isInstalled = PackageUtil.isInstalled(context, app.packageName)
|
||||
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/zip")
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
viewModel.copyInstalledApp(context, app, uri)
|
||||
} else {
|
||||
context.toast(R.string.failed_apk_export)
|
||||
}
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
) {
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (isBlacklisted) R.drawable.ic_cancel else R.drawable.ic_blacklist
|
||||
),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = {
|
||||
val labelRes = if (isBlacklisted) {
|
||||
R.string.action_whitelist
|
||||
} else {
|
||||
R.string.action_blacklist_add
|
||||
}
|
||||
Text(stringResource(labelRes))
|
||||
},
|
||||
selected = false,
|
||||
onClick = {
|
||||
if (isBlacklisted) {
|
||||
viewModel.blacklistProvider.whitelist(app.packageName)
|
||||
context.toast(R.string.toast_apk_whitelisted)
|
||||
} else {
|
||||
viewModel.blacklistProvider.blacklist(app.packageName)
|
||||
context.toast(R.string.toast_apk_blacklisted)
|
||||
}
|
||||
AuroraApp.events.send(BusEvent.Blacklisted(app.packageName))
|
||||
onDismiss()
|
||||
},
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||
)
|
||||
)
|
||||
HorizontalDivider()
|
||||
if (isInstalled) {
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete_forever),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.action_uninstall)) },
|
||||
selected = false,
|
||||
onClick = {
|
||||
AppInstaller.uninstall(context, app.packageName)
|
||||
onDismiss()
|
||||
},
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||
)
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_file_copy),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.action_export)) },
|
||||
selected = false,
|
||||
onClick = { exportLauncher.launch("${app.packageName}.zip") },
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_about),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.action_info)) },
|
||||
selected = false,
|
||||
onClick = {
|
||||
context.openInfo(app.packageName)
|
||||
onDismiss()
|
||||
},
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||
)
|
||||
)
|
||||
HorizontalDivider()
|
||||
androidx.compose.foundation.layout.Spacer(Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user