compose: Initial migration of AppDetails* logic to compose [2/*]
Deprecate setting to hide similar and related apps as they will be always listed in the suggestions pane on widescreen devices Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -194,6 +194,9 @@ dependencies {
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.paging.runtime)
|
||||
|
||||
implementation(libs.androidx.adaptive.core)
|
||||
implementation(libs.androidx.adaptive.navigation)
|
||||
implementation(libs.androidx.adaptive.layout)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
@@ -57,7 +56,6 @@ class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
save(PREFERENCE_THEME_STYLE, 0)
|
||||
save(PREFERENCE_DEFAULT_SELECTED_TAB, 0)
|
||||
save(PREFERENCE_FOR_YOU, true)
|
||||
save(PREFERENCE_SIMILAR, true)
|
||||
|
||||
/*Installer*/
|
||||
save(PREFERENCE_AUTO_DELETE, true)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
|
||||
/**
|
||||
* Returns navigation icon for adaptive screens such as extra pane
|
||||
*/
|
||||
val WindowAdaptiveInfo.adaptiveNavigationIcon: ImageVector
|
||||
get() = when (windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> Icons.AutoMirrored.Filled.ArrowBack
|
||||
else -> Icons.Default.Close
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
|
||||
/**
|
||||
* Composable to show some information
|
||||
* @param modifier Modifier to change the composable
|
||||
* @param title Title of the information
|
||||
* @param description Information to show
|
||||
* @param icon Optional icon representing the information
|
||||
@@ -39,13 +40,14 @@ import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
*/
|
||||
@Composable
|
||||
fun InfoComposable(
|
||||
modifier: Modifier = Modifier,
|
||||
title: AnnotatedString,
|
||||
description: AnnotatedString? = null,
|
||||
@DrawableRes icon: Int? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { if (onClick != null) onClick() }, enabled = onClick != null)
|
||||
.padding(
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
@@ -21,6 +22,7 @@ import com.aurora.store.R
|
||||
/**
|
||||
* A top app bar composable to be used with Scaffold in different Screen
|
||||
* @param title Title of the screen
|
||||
* @param navigationIcon Icon for the navigation button
|
||||
* @param onNavigateUp Action when user clicks the navigation icon
|
||||
* @param actions Actions to display on the top app bar (for e.g. menu)
|
||||
*/
|
||||
@@ -28,17 +30,17 @@ import com.aurora.store.R
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun TopAppBarComposable(
|
||||
title: String? = null,
|
||||
onNavigateUp: () -> Unit,
|
||||
navigationIcon: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
onNavigateUp: (() -> Unit)? = null,
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { if (title != null) Text(text = title) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
if (onNavigateUp != null) {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
Icon(imageVector = navigationIcon, contentDescription = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.composables.preview.coilPreviewProvider
|
||||
|
||||
/**
|
||||
* Composable to show icon for an app that can be animated to also show install progress
|
||||
* @param modifier Modifier to alter the composable
|
||||
* @param iconUrl URL of the app icon
|
||||
* @param progress Progress to show, for e.g. download or install
|
||||
* @param inProgress Whether to show indeterminate or determinate progress bar
|
||||
*/
|
||||
@Composable
|
||||
fun AnimatedAppIconComposable(
|
||||
modifier: Modifier = Modifier,
|
||||
iconUrl: String,
|
||||
progress: Float = 0F,
|
||||
inProgress: Boolean = false
|
||||
) {
|
||||
val animatedScale by animateFloatAsState(
|
||||
targetValue = if (inProgress) 0.75F else 1F,
|
||||
animationSpec = tween(durationMillis = 300)
|
||||
)
|
||||
val clip = when {
|
||||
inProgress -> CircleShape
|
||||
else -> RoundedCornerShape(dimensionResource(R.dimen.radius_medium))
|
||||
}
|
||||
|
||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
||||
if (inProgress) {
|
||||
val indicatorModifier = Modifier.fillMaxSize()
|
||||
if (progress > 0) {
|
||||
CircularProgressIndicator(
|
||||
modifier = indicatorModifier,
|
||||
progress = { progress / 100 }
|
||||
)
|
||||
} else {
|
||||
CircularProgressIndicator(modifier = indicatorModifier)
|
||||
}
|
||||
}
|
||||
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(iconUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(scaleX = animatedScale, scaleY = animatedScale)
|
||||
.clip(clip)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressProvider: PreviewParameterProvider<Float> {
|
||||
override val values: Sequence<Float>
|
||||
get() = sequenceOf(0F, 50F)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
private fun AnimatedAppIconPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
||||
AnimatedAppIconComposable(
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_large)),
|
||||
iconUrl = app.iconArtwork.url
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
private fun AnimatedAppIconPreview(@PreviewParameter(ProgressProvider::class) progress: Float) {
|
||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
||||
AnimatedAppIconComposable(
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_large)),
|
||||
iconUrl = "",
|
||||
inProgress = true,
|
||||
progress = progress
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to show error message when no apps are available for a request
|
||||
* @param message Message for error
|
||||
* @see NoAppAltComposable
|
||||
*/
|
||||
@Composable
|
||||
fun NoAppAltComposable(@StringRes message: Int) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_xsmall))
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NoAppAltComposablePreview() {
|
||||
NoAppAltComposable(message = R.string.details_no_dependencies)
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import com.aurora.store.R
|
||||
* @param message Message for error
|
||||
* @param actionMessage Message to show on action button; defaults to null with button not visible
|
||||
* @param onAction Callback when action button is clicked
|
||||
* @see NoAppAltComposable
|
||||
*/
|
||||
@Composable
|
||||
fun NoAppComposable(
|
||||
|
||||
@@ -11,13 +11,9 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.toRoute
|
||||
import com.aurora.extensions.parentViewModel
|
||||
import com.aurora.store.compose.ui.commons.BlacklistScreen
|
||||
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
||||
import com.aurora.store.compose.ui.details.DetailsExodusScreen
|
||||
import com.aurora.store.compose.ui.details.DetailsMoreScreen
|
||||
import com.aurora.store.compose.ui.details.DetailsReviewScreen
|
||||
import com.aurora.store.compose.ui.details.DetailsScreenshotScreen
|
||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||
|
||||
/**
|
||||
* Navigation graph for compose screens
|
||||
@@ -45,46 +41,21 @@ fun NavGraph(navHostController: NavHostController, startDestination: Screen) {
|
||||
val appDetails = backstackEntry.toRoute<Screen.AppDetails>()
|
||||
AppDetailsScreen(
|
||||
packageName = appDetails.packageName,
|
||||
viewModel = backstackEntry.parentViewModel(navHostController),
|
||||
onNavigateUp = { onNavigateUp() },
|
||||
onNavigateToDetailsMore = { navHostController.navigate(Screen.DetailsMore) },
|
||||
onNavigateToDetailsReview = { navHostController.navigate(Screen.DetailsReview) },
|
||||
onNavigateToDetailsExodus = { navHostController.navigate(Screen.DetailsExodus) },
|
||||
onNavigateToDetailsScreenshot = { index ->
|
||||
navHostController.navigate(
|
||||
Screen.DetailsScreenshot(index)
|
||||
)
|
||||
onNavigateToAppDetails = { packageName ->
|
||||
navHostController.navigate(Screen.AppDetails(packageName))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.DetailsExodus> { backstackEntry ->
|
||||
DetailsExodusScreen(
|
||||
composable<Screen.DevProfile> { backstackEntry ->
|
||||
val devProfile = backstackEntry.toRoute<Screen.DevProfile>()
|
||||
DevProfileScreen(
|
||||
developerId = devProfile.developerId,
|
||||
onNavigateUp = { onNavigateUp() },
|
||||
viewModel = backstackEntry.parentViewModel(navHostController)
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.DetailsMore> { backstackEntry ->
|
||||
DetailsMoreScreen(
|
||||
onNavigateUp = { onNavigateUp() },
|
||||
viewModel = backstackEntry.parentViewModel(navHostController)
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.DetailsScreenshot> { backstackEntry ->
|
||||
val screenshotDetails = backstackEntry.toRoute<Screen.DetailsScreenshot>()
|
||||
DetailsScreenshotScreen(
|
||||
index = screenshotDetails.index,
|
||||
onNavigateUp = { onNavigateUp() },
|
||||
viewModel = backstackEntry.parentViewModel(navHostController)
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.DetailsReview> { backstackEntry ->
|
||||
DetailsReviewScreen(
|
||||
onNavigateUp = { onNavigateUp() },
|
||||
viewModel = backstackEntry.parentViewModel(navHostController)
|
||||
onNavigateToAppDetails = { packageName ->
|
||||
navHostController.navigate(Screen.AppDetails(packageName))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,18 +31,33 @@ sealed class Screen(
|
||||
@Serializable
|
||||
data object Blacklist : Screen(label = R.string.title_blacklist_manager)
|
||||
|
||||
@Serializable
|
||||
data class DevProfile(val developerId: String): Screen()
|
||||
|
||||
@Serializable
|
||||
data class AppDetails(val packageName: String) : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsMore : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data class DetailsScreenshot(val index: Int) : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsExodus : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsReview : Screen()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -25,36 +28,44 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
||||
import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole
|
||||
import androidx.compose.material3.adaptive.navigation.NavigableSupportingPaneScaffold
|
||||
import androidx.compose.material3.adaptive.navigation.rememberSupportingPaneScaffoldNavigator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.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.AnnotatedString
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.extensions.bodyVerySmall
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.copyToClipBoard
|
||||
@@ -68,6 +79,8 @@ import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.HeaderComposable
|
||||
import com.aurora.store.compose.composables.InfoComposable
|
||||
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||
import com.aurora.store.compose.composables.app.AnimatedAppIconComposable
|
||||
import com.aurora.store.compose.composables.app.AppListComposable
|
||||
import com.aurora.store.compose.composables.app.AppProgressComposable
|
||||
import com.aurora.store.compose.composables.app.AppTagComposable
|
||||
import com.aurora.store.compose.composables.app.NoAppComposable
|
||||
@@ -75,6 +88,8 @@ import com.aurora.store.compose.composables.details.RatingComposable
|
||||
import com.aurora.store.compose.composables.details.ScreenshotComposable
|
||||
import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.composables.preview.coilPreviewProvider
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||
import com.aurora.store.compose.ui.dialogs.ManualDownloadDialog
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.Report
|
||||
@@ -84,17 +99,16 @@ import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.PackageUtil.PACKAGE_NAME_GMS
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport
|
||||
|
||||
@Composable
|
||||
fun AppDetailsScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
onNavigateToDetailsMore: () -> Unit,
|
||||
onNavigateToDetailsScreenshot: (index: Int) -> Unit,
|
||||
onNavigateToDetailsReview: () -> Unit,
|
||||
onNavigateToDetailsExodus: () -> Unit,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -104,17 +118,23 @@ fun AppDetailsScreen(
|
||||
val dataSafetyReport by viewModel.dataSafetyReport.collectAsStateWithLifecycle()
|
||||
val plexusScores by viewModel.plexusScores.collectAsStateWithLifecycle()
|
||||
val download by viewModel.download.collectAsStateWithLifecycle()
|
||||
val installProgress by viewModel.installProgress.collectAsStateWithLifecycle()
|
||||
val suggestions by viewModel.suggestions.collectAsStateWithLifecycle()
|
||||
|
||||
var shouldShowManualDownloadDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(key1 = Unit) { viewModel.fetchAppDetails(packageName) }
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
viewModel.purchaseStatus.collect { success ->
|
||||
if (success) {
|
||||
shouldShowManualDownloadDialog = false
|
||||
context.toast(R.string.toast_manual_available)
|
||||
if (shouldShowManualDownloadDialog) {
|
||||
if (success) {
|
||||
shouldShowManualDownloadDialog = false
|
||||
context.toast(R.string.toast_manual_available)
|
||||
} else {
|
||||
context.toast(R.string.toast_manual_unavailable)
|
||||
}
|
||||
} else {
|
||||
context.toast(R.string.toast_manual_unavailable)
|
||||
context.toast(R.string.download_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,19 +155,18 @@ fun AppDetailsScreen(
|
||||
if (this.packageName.isBlank()) {
|
||||
ScreenContentLoading(onNavigateUp = onNavigateUp)
|
||||
} else {
|
||||
ScreenContent(
|
||||
ScreenContentApp(
|
||||
app = this,
|
||||
isAnonymous = viewModel.authProvider.isAnonymous,
|
||||
suggestions = suggestions,
|
||||
download = download,
|
||||
installProgress = installProgress,
|
||||
plexusScores = plexusScores,
|
||||
dataSafetyReport = dataSafetyReport,
|
||||
exodusReport = exodusReport,
|
||||
hasValidUpdate = viewModel.hasValidUpdate,
|
||||
showSimilarApps = viewModel.showSimilarApps,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onNavigateToDetailsMore = onNavigateToDetailsMore,
|
||||
onNavigateToDetailsScreenshot = onNavigateToDetailsScreenshot,
|
||||
onNavigateToDetailsReview = onNavigateToDetailsReview,
|
||||
onNavigateToDetailsExodus = onNavigateToDetailsExodus,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails,
|
||||
onDownload = { viewModel.download(this) },
|
||||
onManualDownload = { shouldShowManualDownloadDialog = true },
|
||||
onCancelDownload = { viewModel.cancelDownload(this) },
|
||||
@@ -160,6 +179,9 @@ fun AppDetailsScreen(
|
||||
} catch (exception: ActivityNotFoundException) {
|
||||
context.toast(context.getString(R.string.unable_to_open))
|
||||
}
|
||||
},
|
||||
onTestingSubscriptionChange = { subscribe ->
|
||||
viewModel.updateTestingProgramStatus(packageName, subscribe)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -171,6 +193,9 @@ fun AppDetailsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to show progress while fetching app details
|
||||
*/
|
||||
@Composable
|
||||
private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) {
|
||||
Scaffold(
|
||||
@@ -180,6 +205,9 @@ private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to display errors related to fetching app details
|
||||
*/
|
||||
@Composable
|
||||
private fun ScreenContentError(onNavigateUp: () -> Unit = {}) {
|
||||
Scaffold(
|
||||
@@ -193,28 +221,186 @@ private fun ScreenContentError(onNavigateUp: () -> Unit = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to display app details and suggestions
|
||||
*/
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
private fun ScreenContentApp(
|
||||
app: App,
|
||||
isAnonymous: Boolean = true,
|
||||
suggestions: List<App> = emptyList(),
|
||||
download: Download? = null,
|
||||
installProgress: Float? = null,
|
||||
plexusScores: Scores? = null,
|
||||
dataSafetyReport: DataSafetyReport? = null,
|
||||
exodusReport: Report? = null,
|
||||
hasValidUpdate: Boolean = false,
|
||||
showSimilarApps: Boolean = false,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
onNavigateToDetailsMore: () -> Unit = {},
|
||||
onNavigateToDetailsScreenshot: (index: Int) -> Unit = {},
|
||||
onNavigateToDetailsReview: () -> Unit = {},
|
||||
onNavigateToDetailsExodus: () -> Unit = {},
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit = {},
|
||||
onDownload: () -> Unit = {},
|
||||
onManualDownload: () -> Unit = {},
|
||||
onCancelDownload: () -> Unit = {},
|
||||
onUninstall: () -> Unit = {},
|
||||
onOpen: () -> Unit = {}
|
||||
onOpen: () -> Unit = {},
|
||||
onTestingSubscriptionChange: (subscribe: Boolean) -> Unit = {}
|
||||
) {
|
||||
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<Screen>()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun showMainPane() {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
fun showExtraPane(screen: Screen) {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, screen)
|
||||
}
|
||||
}
|
||||
|
||||
NavigableSupportingPaneScaffold(
|
||||
navigator = scaffoldNavigator,
|
||||
mainPane = {
|
||||
AnimatedPane {
|
||||
ScreenContentAppMainPane(
|
||||
app = app,
|
||||
isAnonymous = isAnonymous,
|
||||
download = download,
|
||||
installProgress = installProgress,
|
||||
plexusScores = plexusScores,
|
||||
dataSafetyReport = dataSafetyReport,
|
||||
exodusReport = exodusReport,
|
||||
hasValidUpdate = hasValidUpdate,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onDownload = onDownload,
|
||||
onManualDownload = onManualDownload,
|
||||
onCancelDownload = onCancelDownload,
|
||||
onUninstall = onUninstall,
|
||||
onOpen = onOpen,
|
||||
onNavigateToDetailsDevProfile = { showExtraPane(Screen.DevProfile(it)) },
|
||||
onNavigateToDetailsMore = { showExtraPane(Screen.DetailsMore) },
|
||||
onNavigateToDetailsReview = { showExtraPane(Screen.DetailsReview) },
|
||||
onNavigateToDetailsExodus = { showExtraPane(Screen.DetailsExodus) },
|
||||
onNavigateToDetailsScreenshot = { showExtraPane(Screen.DetailsScreenshot(it)) },
|
||||
onTestingSubscriptionChange = onTestingSubscriptionChange
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingPane = {
|
||||
AnimatedPane(modifier = Modifier.safeContentPadding()) {
|
||||
ScreenContentAppSupportingPane(
|
||||
suggestions = suggestions,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
},
|
||||
extraPane = {
|
||||
scaffoldNavigator.currentDestination?.contentKey?.let { screen ->
|
||||
AnimatedPane {
|
||||
when (screen) {
|
||||
is Screen.DetailsReview -> DetailsReviewScreen(onNavigateUp = ::showMainPane)
|
||||
is Screen.DetailsExodus -> DetailsExodusScreen(onNavigateUp = ::showMainPane)
|
||||
is Screen.DetailsMore -> DetailsMoreScreen(
|
||||
onNavigateUp = ::showMainPane,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
|
||||
is Screen.DetailsScreenshot -> DetailsScreenshotScreen(
|
||||
index = screen.index,
|
||||
onNavigateUp = ::showMainPane
|
||||
)
|
||||
// TODO: Pass the real developerId
|
||||
is Screen.DevProfile -> DevProfileScreen(
|
||||
developerId = app.developerName,
|
||||
onNavigateUp = ::showMainPane,
|
||||
onNavigateToAppDetails = { onNavigateToAppDetails(it) }
|
||||
)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to display app details
|
||||
*/
|
||||
@Composable
|
||||
private fun ScreenContentAppMainPane(
|
||||
app: App,
|
||||
download: Download?,
|
||||
installProgress: Float?,
|
||||
isAnonymous: Boolean,
|
||||
plexusScores: Scores?,
|
||||
dataSafetyReport: DataSafetyReport?,
|
||||
exodusReport: Report?,
|
||||
hasValidUpdate: Boolean,
|
||||
onNavigateUp: () -> Unit,
|
||||
onNavigateToDetailsDevProfile: (developerName: String) -> Unit,
|
||||
onNavigateToDetailsMore: () -> Unit,
|
||||
onNavigateToDetailsScreenshot: (index: Int) -> Unit,
|
||||
onNavigateToDetailsReview: () -> Unit,
|
||||
onNavigateToDetailsExodus: () -> Unit,
|
||||
onDownload: () -> Unit,
|
||||
onManualDownload: () -> Unit,
|
||||
onCancelDownload: () -> Unit,
|
||||
onUninstall: () -> Unit,
|
||||
onOpen: () -> Unit,
|
||||
onTestingSubscriptionChange: (subscribe: Boolean) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@Composable
|
||||
fun SetupAppActions() {
|
||||
when {
|
||||
download?.isRunning == true -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_open),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_cancel),
|
||||
isPrimaryActionEnabled = false,
|
||||
onSecondaryAction = onCancelDownload
|
||||
)
|
||||
}
|
||||
|
||||
app.isInstalled && hasValidUpdate -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_update),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
||||
onPrimaryAction = onDownload,
|
||||
onSecondaryAction = onUninstall
|
||||
)
|
||||
}
|
||||
|
||||
app.isInstalled -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_open),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
||||
onPrimaryAction = onOpen,
|
||||
onSecondaryAction = onUninstall
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val primaryActionName = if (PackageUtil.isArchived(context, app.packageName)) {
|
||||
stringResource(R.string.action_unarchive)
|
||||
} else {
|
||||
if (app.isFree) stringResource(R.string.action_install) else app.price
|
||||
}
|
||||
|
||||
AppActions(
|
||||
primaryActionDisplayName = primaryActionName,
|
||||
secondaryActionDisplayName = stringResource(R.string.title_manual_download),
|
||||
onPrimaryAction = onDownload,
|
||||
onSecondaryAction = onManualDownload
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBarComposable(
|
||||
@@ -230,53 +416,24 @@ private fun ScreenContent(
|
||||
.padding(dimensionResource(R.dimen.padding_medium)),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
|
||||
) {
|
||||
// TODO: Deal with download status
|
||||
AppDetails(app = app, hasValidUpdate = hasValidUpdate)
|
||||
|
||||
when {
|
||||
download?.isRunning == true -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_open),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_cancel),
|
||||
isPrimaryActionEnabled = false,
|
||||
onSecondaryAction = onCancelDownload
|
||||
)
|
||||
}
|
||||
|
||||
app.isInstalled && hasValidUpdate -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_update),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
||||
onPrimaryAction = onDownload,
|
||||
onSecondaryAction = onUninstall
|
||||
)
|
||||
}
|
||||
|
||||
app.isInstalled -> {
|
||||
AppActions(
|
||||
primaryActionDisplayName = stringResource(R.string.action_open),
|
||||
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
||||
onPrimaryAction = onOpen,
|
||||
onSecondaryAction = onUninstall
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val primaryActionName = if (PackageUtil.isArchived(context, app.packageName)) {
|
||||
stringResource(R.string.action_unarchive)
|
||||
} else {
|
||||
if (app.isFree) stringResource(R.string.action_install) else app.price
|
||||
}
|
||||
|
||||
AppActions(
|
||||
primaryActionDisplayName = primaryActionName,
|
||||
secondaryActionDisplayName = stringResource(R.string.title_manual_download),
|
||||
onPrimaryAction = onDownload,
|
||||
onSecondaryAction = onManualDownload
|
||||
)
|
||||
}
|
||||
val isDownloading = download != null && download.isRunning
|
||||
val isInstalling = installProgress != null
|
||||
val progress = when {
|
||||
isDownloading -> download?.progress?.toFloat() ?: 0F
|
||||
isInstalling -> installProgress ?: 0F
|
||||
else -> 0F
|
||||
}
|
||||
|
||||
AppDetails(
|
||||
app = app,
|
||||
inProgress = isDownloading || isInstalling,
|
||||
progress = progress,
|
||||
onNavigateToDetailsDevApps = onNavigateToDetailsDevProfile,
|
||||
hasValidUpdate = hasValidUpdate
|
||||
)
|
||||
|
||||
SetupAppActions()
|
||||
|
||||
AppTags(app = app)
|
||||
AppChangelog(changelog = app.changes)
|
||||
HeaderComposable(
|
||||
@@ -292,6 +449,13 @@ private fun ScreenContent(
|
||||
|
||||
AppReviews(rating = app.rating, onNavigateToDetailsReview = onNavigateToDetailsReview)
|
||||
|
||||
if (!isAnonymous && app.testingProgram?.isAvailable == true) {
|
||||
AppTesting(
|
||||
isSubscribed = app.testingProgram!!.isSubscribed,
|
||||
onTestingSubscriptionChange = onTestingSubscriptionChange
|
||||
)
|
||||
}
|
||||
|
||||
AppCompatibility(
|
||||
needsGms = app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS),
|
||||
plexusScores = plexusScores
|
||||
@@ -322,24 +486,67 @@ private fun ScreenContent(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to display similar and related app suggestions
|
||||
*/
|
||||
@Composable
|
||||
private fun ScreenContentAppSupportingPane(
|
||||
suggestions: List<App> = emptyList(),
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit = {}
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { TopAppBarComposable() }
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(dimensionResource(R.dimen.margin_medium)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_suggestions),
|
||||
contentDescription = null
|
||||
)
|
||||
HeaderComposable(title = stringResource(R.string.pref_ui_similar_apps))
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||
) {
|
||||
items(items = suggestions, key = { item -> item.id }) { app ->
|
||||
AppListComposable(
|
||||
app = app,
|
||||
onClick = { onNavigateToAppDetails(app.packageName) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to display basic app details
|
||||
*/
|
||||
@Composable
|
||||
private fun AppDetails(app: App, hasValidUpdate: Boolean = false) {
|
||||
private fun AppDetails(
|
||||
app: App,
|
||||
progress: Float = 0F,
|
||||
inProgress: Boolean = false,
|
||||
onNavigateToDetailsDevApps: (developerName: String) -> Unit,
|
||||
hasValidUpdate: Boolean = false,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.iconArtwork.url)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_large))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
AnimatedAppIconComposable(
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_large)),
|
||||
iconUrl = app.iconArtwork.url,
|
||||
inProgress = inProgress,
|
||||
progress = progress
|
||||
)
|
||||
Column(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))) {
|
||||
Text(
|
||||
@@ -349,6 +556,8 @@ private fun AppDetails(app: App, hasValidUpdate: Boolean = false) {
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = { onNavigateToDetailsDevApps(app.developerName) }),
|
||||
text = app.developerName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
@@ -383,14 +592,20 @@ private fun AppActions(
|
||||
isPrimaryActionEnabled: Boolean = true,
|
||||
isSecondaryActionEnabled: Boolean = true,
|
||||
onPrimaryAction: () -> Unit = {},
|
||||
onSecondaryAction: () -> Unit = {}
|
||||
onSecondaryAction: () -> Unit = {},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium))
|
||||
) {
|
||||
val buttonWidthModifier = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> Modifier.weight(1F)
|
||||
else -> Modifier.widthIn(min = dimensionResource(R.dimen.width_button))
|
||||
}
|
||||
|
||||
FilledTonalButton(
|
||||
modifier = Modifier.weight(1F),
|
||||
modifier = buttonWidthModifier,
|
||||
onClick = onSecondaryAction,
|
||||
enabled = isSecondaryActionEnabled
|
||||
) {
|
||||
@@ -402,7 +617,7 @@ private fun AppActions(
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.weight(1F),
|
||||
modifier = buttonWidthModifier,
|
||||
onClick = onPrimaryAction,
|
||||
enabled = isPrimaryActionEnabled
|
||||
) {
|
||||
@@ -551,7 +766,6 @@ private fun AppReviews(rating: Rating, onNavigateToDetailsReview: () -> Unit) {
|
||||
|
||||
HeaderComposable(
|
||||
title = stringResource(R.string.details_ratings),
|
||||
subtitle = stringResource(R.string.details_ratings_subtitle),
|
||||
onClick = onNavigateToDetailsReview
|
||||
)
|
||||
|
||||
@@ -719,12 +933,57 @@ private fun AppPrivacy(report: Report?, onNavigateToDetailsExodus: (() -> Unit)?
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
/**
|
||||
* Composable to display app's beta testing status
|
||||
*/
|
||||
@Composable
|
||||
private fun AppTesting(
|
||||
isSubscribed: Boolean = false,
|
||||
onTestingSubscriptionChange: (subscribe: Boolean) -> Unit
|
||||
) {
|
||||
HeaderComposable(title = stringResource(R.string.details_beta))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
InfoComposable(
|
||||
modifier = Modifier.weight(1F),
|
||||
icon = R.drawable.ic_experiment,
|
||||
title = AnnotatedString(
|
||||
text = if (isSubscribed) {
|
||||
stringResource(R.string.details_beta_subscribed)
|
||||
} else {
|
||||
stringResource(R.string.details_beta_available)
|
||||
}
|
||||
),
|
||||
description = AnnotatedString(text = stringResource(R.string.details_beta_description))
|
||||
)
|
||||
FilledTonalButton(onClick = { onTestingSubscriptionChange(!isSubscribed) }) {
|
||||
Text(
|
||||
text = if (isSubscribed) {
|
||||
stringResource(R.string.action_leave)
|
||||
} else {
|
||||
stringResource(R.string.action_join)
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewScreenSizes
|
||||
@Composable
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
private fun AppDetailsScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
||||
ScreenContent(app = app, hasValidUpdate = false)
|
||||
ScreenContentApp(
|
||||
app = app,
|
||||
isAnonymous = false,
|
||||
suggestions = List(5) { app.copy(id = Random.nextInt()) },
|
||||
hasValidUpdate = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -20,7 +22,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import com.aurora.Constants.EXODUS_REPORT_URL
|
||||
import com.aurora.extensions.adaptiveNavigationIcon
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
@@ -30,20 +34,31 @@ import com.aurora.store.compose.composables.details.ExodusComposable
|
||||
import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.details.DetailsExodusViewModel
|
||||
|
||||
@Composable
|
||||
fun DetailsExodusScreen(
|
||||
onNavigateUp: () -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel()
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(),
|
||||
detailsExodusViewModel: DetailsExodusViewModel = hiltViewModel { factory: DetailsExodusViewModel.Factory ->
|
||||
factory.create(appDetailsViewModel.exodusReport.value!!)
|
||||
},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val app by viewModel.app.collectAsStateWithLifecycle()
|
||||
val exodusReport by viewModel.exodusReport.collectAsStateWithLifecycle()
|
||||
val app by appDetailsViewModel.app.collectAsStateWithLifecycle()
|
||||
val exodusReport by appDetailsViewModel.exodusReport.collectAsStateWithLifecycle()
|
||||
val trackers by detailsExodusViewModel.trackers.collectAsStateWithLifecycle()
|
||||
|
||||
val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> app!!.displayName
|
||||
else -> stringResource(R.string.details_privacy)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
topAppBarTitle = app!!.displayName,
|
||||
topAppBarTitle = topAppBarTitle,
|
||||
id = exodusReport!!.id,
|
||||
version = exodusReport!!.version,
|
||||
trackers = viewModel.getExodusTrackersFromReport(),
|
||||
trackers = trackers,
|
||||
onNavigateUp = onNavigateUp,
|
||||
)
|
||||
}
|
||||
@@ -55,12 +70,17 @@ private fun ScreenContent(
|
||||
version: String = String(),
|
||||
trackers: List<ExodusTracker> = emptyList(),
|
||||
onNavigateUp: () -> Unit = {},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBarComposable(title = topAppBarTitle, onNavigateUp = onNavigateUp)
|
||||
TopAppBarComposable(
|
||||
title = topAppBarTitle,
|
||||
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
|
||||
@@ -8,12 +8,17 @@ package com.aurora.store.compose.ui.details
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -26,32 +31,63 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import com.aurora.extensions.adaptiveNavigationIcon
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.HeaderComposable
|
||||
import com.aurora.store.compose.composables.InfoComposable
|
||||
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||
import com.aurora.store.compose.composables.app.AppComposable
|
||||
import com.aurora.store.compose.composables.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.composables.preview.coilPreviewProvider
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.details.DetailsMoreViewModel
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun DetailsMoreScreen(
|
||||
onNavigateUp: () -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel()
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(),
|
||||
detailsMoreViewModel: DetailsMoreViewModel = hiltViewModel { factory: DetailsMoreViewModel.Factory ->
|
||||
factory.create(appDetailsViewModel.app.value!!.dependencies.dependentPackages)
|
||||
}
|
||||
) {
|
||||
val app by viewModel.app.collectAsStateWithLifecycle()
|
||||
val app by appDetailsViewModel.app.collectAsStateWithLifecycle()
|
||||
val dependencies by detailsMoreViewModel.dependentApps.collectAsStateWithLifecycle()
|
||||
|
||||
ScreenContent(app = app!!, onNavigateUp = onNavigateUp)
|
||||
ScreenContent(
|
||||
app = app!!,
|
||||
dependencies = dependencies,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(app: App, onNavigateUp: () -> Unit = {}) {
|
||||
private fun ScreenContent(
|
||||
app: App,
|
||||
dependencies: List<App>? = null,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit = {},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> app.displayName
|
||||
else -> stringResource(R.string.details_more_about_app)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { TopAppBarComposable(title = app.displayName, onNavigateUp = onNavigateUp) }
|
||||
topBar = {
|
||||
TopAppBarComposable(
|
||||
title = topAppBarTitle,
|
||||
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -69,51 +105,88 @@ private fun ScreenContent(app: App, onNavigateUp: () -> Unit = {}) {
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
if (app.appInfo.appInfoMap.isNotEmpty()) {
|
||||
HeaderComposable(title = stringResource(R.string.details_more_info))
|
||||
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_package_name)
|
||||
),
|
||||
description = AnnotatedString(text = app.packageName)
|
||||
if (dependencies != null) {
|
||||
AppDependencies(
|
||||
dependencies = dependencies,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_content_rating)
|
||||
),
|
||||
description = AnnotatedString(text = app.contentRating.title)
|
||||
)
|
||||
AppInfoMore(app = app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.appInfo.appInfoMap.forEach { (title, subtitle) ->
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = title.replace("_", " ")
|
||||
.lowercase(Locale.getDefault())
|
||||
.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(Locale.getDefault())
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
),
|
||||
description = AnnotatedString(text = subtitle)
|
||||
)
|
||||
}
|
||||
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_target_api)
|
||||
),
|
||||
description = AnnotatedString(text = "API ${app.targetSdk}")
|
||||
/**
|
||||
* Composable to show dependencies of an app
|
||||
*/
|
||||
@Composable
|
||||
private fun AppDependencies(
|
||||
dependencies: List<App>,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit
|
||||
) {
|
||||
HeaderComposable(title = stringResource(R.string.details_dependencies))
|
||||
if (dependencies.isEmpty()) {
|
||||
InfoComposable(
|
||||
title = AnnotatedString(text = stringResource(R.string.details_no_dependencies))
|
||||
)
|
||||
} else {
|
||||
LazyRow(modifier = Modifier.fillMaxWidth()) {
|
||||
items(items = dependencies, key = { item -> item.id }) { app ->
|
||||
AppComposable(
|
||||
app = app,
|
||||
onClick = { onNavigateToAppDetails(app.packageName) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to show more information about the app that maybe advanced
|
||||
*/
|
||||
@Composable
|
||||
private fun AppInfoMore(app: App) {
|
||||
HeaderComposable(title = stringResource(R.string.details_more_info))
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_package_name)
|
||||
),
|
||||
description = AnnotatedString(text = app.packageName)
|
||||
)
|
||||
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_target_api)
|
||||
),
|
||||
description = AnnotatedString(text = "API ${app.targetSdk}")
|
||||
)
|
||||
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = stringResource(R.string.details_more_content_rating)
|
||||
),
|
||||
description = AnnotatedString(text = app.contentRating.title)
|
||||
)
|
||||
|
||||
app.appInfo.appInfoMap.forEach { (title, subtitle) ->
|
||||
InfoComposable(
|
||||
title = AnnotatedString(
|
||||
text = title.replace("_", " ")
|
||||
.lowercase(Locale.getDefault())
|
||||
.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(Locale.getDefault())
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
),
|
||||
description = AnnotatedString(text = subtitle)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
|
||||
@@ -18,6 +18,8 @@ import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -34,26 +36,38 @@ import androidx.paging.PagingData
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import com.aurora.extensions.adaptiveNavigationIcon
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||
import com.aurora.store.compose.composables.details.ReviewComposable
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.review.DetailsReviewViewModel
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
@Composable
|
||||
fun DetailsReviewScreen(
|
||||
onNavigateUp: () -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel()
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(),
|
||||
detailsReviewViewModel: DetailsReviewViewModel = hiltViewModel { factory: DetailsReviewViewModel.Factory ->
|
||||
factory.create(appDetailsViewModel.app.value!!.packageName)
|
||||
},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val app by viewModel.app.collectAsStateWithLifecycle()
|
||||
val reviews = viewModel.reviews.collectAsLazyPagingItems()
|
||||
val app by appDetailsViewModel.app.collectAsStateWithLifecycle()
|
||||
val reviews = detailsReviewViewModel.reviews.collectAsLazyPagingItems()
|
||||
|
||||
val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> app!!.displayName
|
||||
else -> stringResource(R.string.details_ratings)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
topAppBarTitle = app!!.displayName,
|
||||
topAppBarTitle = topAppBarTitle,
|
||||
reviews = reviews,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onFilter = { filter -> viewModel.fetchReviews(filter) }
|
||||
onFilter = { filter -> detailsReviewViewModel.fetchReviews(filter) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,12 +76,17 @@ private fun ScreenContent(
|
||||
topAppBarTitle: String? = null,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
reviews: LazyPagingItems<Review>,
|
||||
onFilter: (filter: Review.Filter) -> Unit = {}
|
||||
onFilter: (filter: Review.Filter) -> Unit = {},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBarComposable(title = topAppBarTitle, onNavigateUp = onNavigateUp)
|
||||
TopAppBarComposable(
|
||||
title = topAppBarTitle,
|
||||
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
@@ -90,7 +109,7 @@ private fun ScreenContent(
|
||||
count = reviews.itemCount,
|
||||
key = reviews.itemKey { it.commentId }
|
||||
) { index ->
|
||||
ReviewComposable(review = reviews[index]!!)
|
||||
reviews[index]?.let { review -> ReviewComposable(review = review) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -18,6 +21,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import com.aurora.extensions.adaptiveNavigationIcon
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||
@@ -28,12 +33,18 @@ import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
fun DetailsScreenshotScreen(
|
||||
index: Int,
|
||||
onNavigateUp: () -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel()
|
||||
viewModel: AppDetailsViewModel = hiltViewModel(),
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val app by viewModel.app.collectAsStateWithLifecycle()
|
||||
|
||||
val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> app!!.displayName
|
||||
else -> stringResource(R.string.details_more_about_app)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
topAppBarTitle = app!!.displayName,
|
||||
topAppBarTitle = topAppBarTitle,
|
||||
onNavigateUp = onNavigateUp,
|
||||
screenshots = app!!.screenshots,
|
||||
index = index
|
||||
@@ -45,14 +56,23 @@ private fun ScreenContent(
|
||||
topAppBarTitle: String? = null,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
screenshots: List<Artwork> = emptyList(),
|
||||
index: Int = 0
|
||||
index: Int = 0,
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val displayMetrics = LocalContext.current.resources.displayMetrics
|
||||
val pagerState = rememberPagerState(initialPage = index) { screenshots.size }
|
||||
|
||||
LaunchedEffect(key1 = index) {
|
||||
if (pagerState.currentPage != index) pagerState.scrollToPage(index)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBarComposable(title = topAppBarTitle, onNavigateUp = onNavigateUp)
|
||||
TopAppBarComposable(
|
||||
title = topAppBarTitle,
|
||||
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
HorizontalPager(
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.dev
|
||||
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.aurora.extensions.adaptiveNavigationIcon
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||
import com.aurora.store.viewmodel.details.DevProfileViewModel
|
||||
|
||||
@Composable
|
||||
fun DevProfileScreen(
|
||||
developerId: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
devProfileViewModel: DevProfileViewModel = hiltViewModel()
|
||||
) {
|
||||
|
||||
ScreenContent(
|
||||
topAppBarTitle = "",
|
||||
onNavigateUp = onNavigateUp,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
topAppBarTitle: String? = null,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit = {},
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBarComposable(
|
||||
title = topAppBarTitle,
|
||||
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(dimensionResource(R.dimen.padding_medium)),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DevProfileScreenPreview() {
|
||||
ScreenContent()
|
||||
}
|
||||
@@ -26,7 +26,6 @@ sealed class BusEvent : Event() {
|
||||
lateinit var error: String
|
||||
|
||||
data class Blacklisted(val packageName: String) : BusEvent()
|
||||
data class ManualDownload(val packageName: String, val versionCode: Long) : BusEvent()
|
||||
}
|
||||
|
||||
sealed class AuthEvent : Event() {
|
||||
|
||||
@@ -35,7 +35,6 @@ object Preferences {
|
||||
const val PREFERENCE_THEME_STYLE = "PREFERENCE_THEME_STYLE"
|
||||
const val PREFERENCE_FOR_YOU = "PREFERENCE_FOR_YOU"
|
||||
const val PREFERENCE_DEFAULT_SELECTED_TAB = "PREFERENCE_DEFAULT_SELECTED_TAB"
|
||||
const val PREFERENCE_SIMILAR = "PREFERENCE_SIMILAR"
|
||||
const val PREFERENCE_INTRO = "PREFERENCE_INTRO"
|
||||
|
||||
const val PREFERENCE_FILTER_FDROID = "PREFERENCE_FILTER_FDROID"
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.controller
|
||||
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup
|
||||
import com.aurora.store.view.epoxy.groups.DeveloperModelGroup
|
||||
|
||||
open class DeveloperCarouselController(private val callbacks: Callbacks) :
|
||||
GenericCarouselController(callbacks) {
|
||||
|
||||
override fun applyFilter(streamBundle: StreamCluster): Boolean {
|
||||
return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster
|
||||
&& streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters
|
||||
}
|
||||
|
||||
override fun buildModels(streamBundle: StreamBundle?) {
|
||||
setFilterDuplicates(true)
|
||||
if (streamBundle == null) {
|
||||
for (i in 1..2) {
|
||||
add(
|
||||
CarouselShimmerGroup()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
streamBundle
|
||||
.streamClusters
|
||||
.values
|
||||
.filter { applyFilter(it) }
|
||||
.forEach { streamCluster ->
|
||||
add(DeveloperModelGroup(streamCluster, callbacks))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.groups
|
||||
|
||||
import android.util.Log
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.EpoxyModelGroup
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
|
||||
class DeveloperModelGroup(
|
||||
streamCluster: StreamCluster,
|
||||
callbacks: GenericCarouselController.Callbacks
|
||||
) :
|
||||
EpoxyModelGroup(
|
||||
R.layout.model_developer_carousel_group, buildModels(
|
||||
streamCluster,
|
||||
callbacks
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "DeveloperModelGroup"
|
||||
|
||||
private fun buildModels(
|
||||
streamCluster: StreamCluster,
|
||||
callbacks: GenericCarouselController.Callbacks
|
||||
): List<EpoxyModel<*>> {
|
||||
val models = ArrayList<EpoxyModel<*>>()
|
||||
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
val screenshotsViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
val idPrefix = streamCluster.id
|
||||
|
||||
models.add(
|
||||
HeaderViewModel_()
|
||||
.id("${idPrefix}_header")
|
||||
.title(streamCluster.clusterTitle)
|
||||
.browseUrl(streamCluster.clusterBrowseUrl)
|
||||
.click { _ ->
|
||||
callbacks.onHeaderClicked(streamCluster)
|
||||
}
|
||||
)
|
||||
|
||||
if (streamCluster.clusterAppList.size == 1) {
|
||||
val app = streamCluster.clusterAppList[0]
|
||||
|
||||
for (artwork in app.screenshots) {
|
||||
screenshotsViewModels.add(
|
||||
ScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.artwork(artwork)
|
||||
)
|
||||
}
|
||||
|
||||
clusterViewModels.add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ ->
|
||||
callbacks.onAppClick(app)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
for (app in streamCluster.clusterAppList) {
|
||||
clusterViewModels.add(
|
||||
AppViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ ->
|
||||
callbacks.onAppClick(app)
|
||||
}
|
||||
.longClick { _ ->
|
||||
callbacks.onAppLongClick(app)
|
||||
false
|
||||
}
|
||||
.onBind { _, _, position ->
|
||||
val itemCount = clusterViewModels.count()
|
||||
if (itemCount >= 2) {
|
||||
if (position == clusterViewModels.count() - 2) {
|
||||
callbacks.onClusterScrolled(streamCluster)
|
||||
Log.i(TAG, "Cluster ${streamCluster.clusterTitle} Scrolled")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (screenshotsViewModels.isNotEmpty()) {
|
||||
models.add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("${idPrefix}_screenshots")
|
||||
.models(screenshotsViewModels)
|
||||
)
|
||||
}
|
||||
|
||||
models.add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("${idPrefix}_cluster")
|
||||
.models(clusterViewModels)
|
||||
)
|
||||
|
||||
return models
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.databinding.ViewExodusBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class ExodusView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewExodusBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun tracker(report: ExodusTracker) {
|
||||
binding.txtTitle.text = report.name
|
||||
binding.txtSubtitle.text = report.signature
|
||||
binding.txtDescription.text = report.date
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
binding.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@OnViewRecycled
|
||||
fun clear() {
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.gplayapi.data.models.PlayFile
|
||||
import com.aurora.store.databinding.ViewFileBinding
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class FileView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewFileBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun file(file: PlayFile) {
|
||||
binding.line1.text = file.name
|
||||
binding.line2.text = CommonUtil.addSiPrefix(file.size)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.util.AttributeSet
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewScreenshotLargeBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class LargeScreenshotView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewScreenshotLargeBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
val displayMetrics = Resources.getSystem().displayMetrics
|
||||
binding.img.load("${artwork.url}=rw-w${displayMetrics.widthPixels}-v1-e15") {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewScreenshotMiniBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class MiniScreenshotView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewScreenshotMiniBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
private var position: Int = 0
|
||||
|
||||
interface ScreenshotCallback {
|
||||
fun onClick(position: Int = 0)
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun position(pos: Int) {
|
||||
position = pos
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
normalizeSize(artwork)
|
||||
binding.img.load("${artwork.url}=rw-w480-v1-e15") {
|
||||
placeholder(R.drawable.bg_rounded)
|
||||
transformations(RoundedCornersTransformation(8.px.toFloat()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeSize(artwork: Artwork) {
|
||||
if (artwork.height != 0 && artwork.width != 0) {
|
||||
|
||||
val artworkHeight = artwork.height
|
||||
val artworkWidth = artwork.width
|
||||
|
||||
val normalizedHeight: Float
|
||||
val normalizedWidth: Float
|
||||
|
||||
when {
|
||||
artworkHeight == artworkWidth -> {
|
||||
normalizedHeight = 120f
|
||||
normalizedWidth = 120f
|
||||
}
|
||||
|
||||
else -> {
|
||||
val factor = artworkHeight / 120f
|
||||
normalizedHeight = 120f
|
||||
normalizedWidth = (artworkWidth / factor)
|
||||
}
|
||||
}
|
||||
|
||||
binding.img.layoutParams.height = normalizedHeight.px.toInt()
|
||||
binding.img.layoutParams.width = normalizedWidth.px.toInt()
|
||||
binding.img.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun callback(screenshotCallback: ScreenshotCallback?) {
|
||||
binding.img.setOnClickListener {
|
||||
screenshotCallback?.onClick(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.extensions.toDate
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewReviewBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class ReviewView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewReviewBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun review(review: Review) {
|
||||
binding.txtAuthor.text = review.userName
|
||||
binding.txtTime.text = ("${review.timeStamp.toDate()} • v${review.appVersion}")
|
||||
binding.txtComment.text = review.comment
|
||||
|
||||
binding.img.load(review.userPhotoUrl) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(32F))
|
||||
}
|
||||
|
||||
binding.rating.rating = review.rating.toFloat()
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
binding.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun longClick(onClickListener: OnLongClickListener?) {
|
||||
binding.root.setOnLongClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewScreenshotBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class ScreenshotView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewScreenshotBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
private var position: Int = 0
|
||||
|
||||
interface ScreenshotCallback {
|
||||
fun onClick(position: Int = 0)
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun position(pos: Int) {
|
||||
position = pos
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
normalizeSize(artwork)
|
||||
binding.img.load("${artwork.url}=rw-w480-v1-e15") {
|
||||
placeholder(R.drawable.bg_rounded)
|
||||
transformations(RoundedCornersTransformation(8.px.toFloat()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeSize(artwork: Artwork) {
|
||||
if (artwork.height != 0 && artwork.width != 0) {
|
||||
|
||||
val artworkHeight = artwork.height
|
||||
val artworkWidth = artwork.width
|
||||
|
||||
val normalizedHeight: Float
|
||||
val normalizedWidth: Float
|
||||
|
||||
when {
|
||||
artworkHeight == artworkWidth -> {
|
||||
normalizedHeight = 192f
|
||||
normalizedWidth = 192f
|
||||
}
|
||||
|
||||
else -> {
|
||||
val factor = artworkHeight / 192f
|
||||
normalizedHeight = 192f
|
||||
normalizedWidth = (artworkWidth / factor)
|
||||
}
|
||||
}
|
||||
|
||||
binding.img.layoutParams.height = normalizedHeight.px.toInt()
|
||||
binding.img.layoutParams.width = normalizedWidth.px.toInt()
|
||||
binding.img.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun callback(screenshotCallback: ScreenshotCallback?) {
|
||||
binding.img.setOnClickListener {
|
||||
screenshotCallback?.onClick(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,15 +121,6 @@ abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
fun openScreenshotFragment(app: App, position: Int) {
|
||||
findNavController().navigate(
|
||||
MobileNavigationDirections.actionGlobalScreenshotFragment(
|
||||
position,
|
||||
app.screenshots.toTypedArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun openAppMenuSheet(app: MinimalApp) {
|
||||
findNavController().navigate(MobileNavigationDirections.actionGlobalAppMenuSheet(app))
|
||||
}
|
||||
|
||||
@@ -24,15 +24,11 @@ import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.groups.CarouselHorizontalModel_
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.browse.ExpandedStreamBrowseViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -92,30 +88,6 @@ class ExpandedStreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBind
|
||||
}
|
||||
} else {
|
||||
streamCluster.clusterAppList.forEach {
|
||||
val screenshotsViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
for ((position, artwork) in it.screenshots.withIndex()) {
|
||||
screenshotsViewModels.add(
|
||||
MiniScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.position(position)
|
||||
.artwork(artwork)
|
||||
.callback(object : MiniScreenshotView.ScreenshotCallback {
|
||||
override fun onClick(position: Int) {
|
||||
openScreenshotFragment(it, position)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (screenshotsViewModels.isNotEmpty()) {
|
||||
add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("${it.id}_screenshots")
|
||||
.models(screenshotsViewModels)
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(it.packageName.hashCode())
|
||||
|
||||
@@ -63,7 +63,6 @@ import com.aurora.store.AppStreamStash
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.MainActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
@@ -77,14 +76,11 @@ import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.FlavouredUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.ShortcutManagerUtil
|
||||
import com.aurora.store.view.custom.RatingView
|
||||
import com.aurora.store.view.epoxy.controller.DetailsCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.details.DetailsClusterViewModel
|
||||
@@ -114,8 +110,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
|
||||
private val isExtendedUpdateEnabled: Boolean
|
||||
get() = Preferences.getBoolean(requireContext(), PREFERENCE_UPDATES_EXTENDED)
|
||||
private val showSimilarApps: Boolean
|
||||
get() = Preferences.getBoolean(requireContext(), PREFERENCE_SIMILAR)
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
@@ -131,20 +125,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
is BusEvent.ManualDownload -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
val requestedApp = app.copy(
|
||||
versionCode = event.versionCode,
|
||||
dependencies = app.dependencies.copy(
|
||||
dependentLibraries = app.dependencies.dependentLibraries.map { lib ->
|
||||
lib.copy(versionCode = event.versionCode)
|
||||
}
|
||||
)
|
||||
)
|
||||
purchase(requestedApp)
|
||||
}
|
||||
}
|
||||
|
||||
is InstallerEvent.Failed -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
findNavController().navigate(
|
||||
@@ -241,16 +221,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
text =
|
||||
"${report.trackers.size} ${getString(R.string.exodus_substring)} ${report.version}"
|
||||
}
|
||||
|
||||
binding.layoutDetailsPrivacy.headerPrivacy.addClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections
|
||||
.actionAppDetailsFragmentToDetailsExodusFragment(
|
||||
app.displayName,
|
||||
report
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.layoutDetailsPrivacy.txtStatus.apply {
|
||||
setTextColor(ContextCompat.getColor(requireContext(), R.color.colorGreen))
|
||||
@@ -618,16 +588,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
purchase(app)
|
||||
}
|
||||
}
|
||||
|
||||
binding.layoutDetailsApp.btnSecondaryAction.apply {
|
||||
text = getString(R.string.title_manual_download)
|
||||
setOnClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections
|
||||
.actionAppDetailsFragmentToManualDownloadSheet(app)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lay out the toolbar again
|
||||
@@ -651,7 +611,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
private fun updateExtraDetails(app: App) {
|
||||
binding.viewFlipper.displayedChild = 1
|
||||
|
||||
updateAppDescription(app)
|
||||
updateAppRatingAndReviews(app)
|
||||
@@ -671,10 +630,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
updateBetaSubscription(app)
|
||||
}
|
||||
|
||||
if (showSimilarApps) {
|
||||
updateAppStream(app)
|
||||
}
|
||||
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
@@ -706,45 +661,11 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
headerDescription.addClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToDetailsMoreFragment(app)
|
||||
)
|
||||
}
|
||||
|
||||
epoxyRecycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
var position = 0
|
||||
app.screenshots
|
||||
//.sortedWith { o1, o2 -> o2.height.compareTo(o1.height) }
|
||||
.forEach { artwork ->
|
||||
add(
|
||||
ScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.artwork(artwork)
|
||||
.position(position++)
|
||||
.callback(object : ScreenshotView.ScreenshotCallback {
|
||||
override fun onClick(position: Int) {
|
||||
openScreenshotFragment(app, position)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAppRatingAndReviews(app: App) {
|
||||
binding.layoutDetailsReview.apply {
|
||||
headerRatingReviews.addClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToDetailsReviewFragment(
|
||||
app.displayName,
|
||||
app.packageName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var totalStars = 0L
|
||||
totalStars += app.rating.oneStar
|
||||
@@ -833,15 +754,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
imgBeta.load(betaProgram.artwork.url) {
|
||||
|
||||
}
|
||||
|
||||
btnBetaAction.setOnClickListener {
|
||||
btnBetaAction.text = getString(R.string.action_pending)
|
||||
btnBetaAction.isEnabled = false
|
||||
viewModel.fetchTestingProgramStatus(
|
||||
app.packageName,
|
||||
!betaProgram.isSubscribed
|
||||
)
|
||||
}
|
||||
} else {
|
||||
root.hide()
|
||||
}
|
||||
@@ -956,7 +868,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
|
||||
private fun updateCompatibilityInfo() {
|
||||
if (app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
viewModel.fetchPlexusReport(app.packageName)
|
||||
|
||||
binding.layoutDetailsCompatibility.txtGmsDependency.apply {
|
||||
title = getString(R.string.details_compatibility_gms_required_title)
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ExodusViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.details.DetailsExodusViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsExodusFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
|
||||
private val viewModel: DetailsExodusViewModel by viewModels()
|
||||
private val args: DetailsExodusFragmentArgs by navArgs()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
title = args.displayName
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
updateController(getExodusTrackersFromReport(args.report))
|
||||
}
|
||||
|
||||
private fun updateController(reviews: List<ExodusTracker>) {
|
||||
binding.recycler.withModels {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("header")
|
||||
.title(getString(R.string.exodus_view_report))
|
||||
.browseUrl("browse")
|
||||
.click { _ -> context?.browse(Constants.EXODUS_REPORT_URL + args.report.id) }
|
||||
)
|
||||
reviews.forEach {
|
||||
add(
|
||||
ExodusViewModel_()
|
||||
.id(it.id)
|
||||
.tracker(it)
|
||||
.click { _ ->
|
||||
context?.browse(it.url)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExodusTrackersFromReport(report: Report): List<ExodusTracker> {
|
||||
val trackerObjects = report.trackers.map {
|
||||
viewModel.exodusTrackers.getJSONObject(it.toString())
|
||||
}.toList()
|
||||
|
||||
return trackerObjects.map {
|
||||
ExodusTracker(
|
||||
id = it.getInt("id"),
|
||||
name = it.getString("name"),
|
||||
url = it.getString("website"),
|
||||
signature = it.getString("code_signature"),
|
||||
date = it.getString("creation_date"),
|
||||
description = it.getString("description"),
|
||||
networkSignature = it.getString("network_signature"),
|
||||
documentation = listOf(it.getString("documentation")),
|
||||
categories = listOf(it.getString("categories"))
|
||||
)
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentDetailsMoreBinding
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.NoAppAltViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.AppDependentViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.FileViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.InfoViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.MoreBadgeViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.details.DetailsMoreViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsMoreFragment : BaseFragment<FragmentDetailsMoreBinding>() {
|
||||
|
||||
private val viewModel: DetailsMoreViewModel by viewModels()
|
||||
private val args: DetailsMoreFragmentArgs by navArgs()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Adjust layout for edgeToEdge display
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerMore) { layout, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
layout.setPadding(0, 0, 0, insets.bottom)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.setOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
inflateDescription(args.app)
|
||||
inflateFiles(args.app)
|
||||
viewModel.fetchDependentApps(args.app)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.dependentApps.collect { list ->
|
||||
if (list.isNotEmpty()) {
|
||||
binding.recyclerDependency.withModels {
|
||||
list.filter { it.displayName.isNotEmpty() }.forEach {
|
||||
add(
|
||||
AppDependentViewModel_()
|
||||
.id(it.id)
|
||||
.app(it)
|
||||
.click { _ -> openDetailsFragment(it.packageName, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.recyclerDependency.withModels {
|
||||
add(
|
||||
NoAppAltViewModel_()
|
||||
.id("no_app")
|
||||
.message(getString(R.string.details_no_dependencies))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateDescription(app: App) {
|
||||
binding.toolbar.title = app.displayName
|
||||
binding.txtDescription.text = HtmlCompat.fromHtml(
|
||||
app.description,
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
}
|
||||
|
||||
private fun inflateFiles(app: App) {
|
||||
binding.recyclerMore.withModels {
|
||||
//Add dependent files
|
||||
if (app.fileList.isNotEmpty()) {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("badge_header")
|
||||
.title("Files")
|
||||
)
|
||||
|
||||
app.fileList.forEach {
|
||||
add(
|
||||
FileViewModel_()
|
||||
.id(it.id)
|
||||
.file(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//Add display & extra badges
|
||||
if (app.infoBadges.isNotEmpty()) {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("badge_header")
|
||||
.title("More")
|
||||
)
|
||||
|
||||
app.infoBadges.forEach {
|
||||
add(
|
||||
MoreBadgeViewModel_()
|
||||
.id(it.id)
|
||||
.badge(it)
|
||||
)
|
||||
}
|
||||
|
||||
if (app.displayBadges.isNotEmpty()) {
|
||||
app.displayBadges
|
||||
.filter { it.textMajor.isNotEmpty() }
|
||||
.forEach {
|
||||
add(
|
||||
MoreBadgeViewModel_()
|
||||
.id(it.id)
|
||||
.badge(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app.appInfo.appInfoMap.isNotEmpty()) {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("info_header")
|
||||
.title("Info")
|
||||
)
|
||||
app.appInfo.appInfoMap.forEach {
|
||||
add(
|
||||
InfoViewModel_()
|
||||
.id(it.key)
|
||||
.badge(it)
|
||||
)
|
||||
}
|
||||
add(
|
||||
InfoViewModel_()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.badge(mapOf("TARGET" to "${app.targetSdk}").entries.first())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.ReviewCluster
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentDetailsReviewBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ReviewViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.review.ReviewViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsReviewFragment : BaseFragment<FragmentDetailsReviewBinding>() {
|
||||
|
||||
private val args: DetailsReviewFragmentArgs by navArgs()
|
||||
private val viewModel: ReviewViewModel by viewModels()
|
||||
|
||||
private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener
|
||||
private lateinit var filter: Review.Filter
|
||||
private lateinit var reviewCluster: ReviewCluster
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Adjust layout for edgeToEdge display
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.recycler) { layout, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
layout.setPadding(0, 0, 0, insets.bottom)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
title = args.displayName
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
if (!::reviewCluster.isInitialized) {
|
||||
endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
if (::reviewCluster.isInitialized) {
|
||||
viewModel.next(reviewCluster.nextPageUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
}
|
||||
|
||||
it?.let {
|
||||
reviewCluster = it
|
||||
updateController(reviewCluster)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Reviews
|
||||
filter = Review.Filter.ALL
|
||||
viewModel.fetchReview(args.packageName, filter)
|
||||
|
||||
// Chips
|
||||
binding.chipGroup.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
when (checkedIds[0]) {
|
||||
R.id.filter_review_all -> filter = Review.Filter.ALL
|
||||
R.id.filter_newest_first -> filter = Review.Filter.NEWEST
|
||||
R.id.filter_review_critical -> filter = Review.Filter.CRITICAL
|
||||
R.id.filter_review_positive -> filter = Review.Filter.POSITIVE
|
||||
R.id.filter_review_five -> filter = Review.Filter.FIVE
|
||||
R.id.filter_review_four -> filter = Review.Filter.FOUR
|
||||
R.id.filter_review_three -> filter = Review.Filter.THREE
|
||||
R.id.filter_review_two -> filter = Review.Filter.TWO
|
||||
R.id.filter_review_one -> filter = Review.Filter.ONE
|
||||
}
|
||||
|
||||
endlessRecyclerOnScrollListener.resetPageCount()
|
||||
viewModel.fetchReview(args.packageName, filter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateController(reviewCluster: ReviewCluster) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
reviewCluster.reviewList.forEach {
|
||||
add(
|
||||
ReviewViewModel_()
|
||||
.id(it.commentId)
|
||||
.review(it)
|
||||
)
|
||||
}
|
||||
|
||||
if (reviewCluster.hasNext()) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import com.aurora.gplayapi.data.models.details.DevStream
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.databinding.FragmentDevProfileBinding
|
||||
import com.aurora.store.view.epoxy.controller.DeveloperCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.details.DevProfileViewModel
|
||||
@@ -47,7 +46,6 @@ class DevProfileFragment : BaseFragment<FragmentDevProfileBinding>(),
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val developerCarouselController = DeveloperCarouselController(this)
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
@@ -56,7 +54,6 @@ class DevProfileFragment : BaseFragment<FragmentDevProfileBinding>(),
|
||||
}
|
||||
|
||||
// RecyclerView
|
||||
binding.recycler.setController(developerCarouselController)
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
when (it) {
|
||||
@@ -82,7 +79,6 @@ class DevProfileFragment : BaseFragment<FragmentDevProfileBinding>(),
|
||||
binding.txtDevDescription.text = description
|
||||
binding.imgIcon.load(imgUrl)
|
||||
binding.viewFlipper.displayedChild = 0
|
||||
developerCarouselController.setData(streamBundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentScreenshotBinding
|
||||
import com.aurora.store.view.epoxy.views.details.LargeScreenshotViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScreenshotFragment : BaseFragment<FragmentScreenshotBinding>() {
|
||||
|
||||
private val args: ScreenshotFragmentArgs by navArgs()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
elevation = 0f
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
// Recycler View
|
||||
binding.recyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
|
||||
PagerSnapHelper().attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
updateController(args.arrayOfArtwork, args.position)
|
||||
}
|
||||
|
||||
private fun updateController(artworks: Array<Artwork>, position: Int) {
|
||||
binding.recyclerView.withModels {
|
||||
artworks.forEach {
|
||||
add(
|
||||
LargeScreenshotViewModel_()
|
||||
.id(it.url)
|
||||
.artwork(it)
|
||||
)
|
||||
}
|
||||
binding.recyclerView.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2019, 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/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package com.aurora.store.view.ui.sheets
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.SheetManualDownloadBinding
|
||||
import com.aurora.store.viewmodel.sheets.ManualDownloadViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ManualDownloadSheet : BaseDialogSheet<SheetManualDownloadBinding>() {
|
||||
private val viewModel: ManualDownloadViewModel by viewModels()
|
||||
private val args: ManualDownloadSheetArgs by navArgs()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
isCancelable = false
|
||||
|
||||
inflateData()
|
||||
attachActions()
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.purchaseStatus.collectLatest {
|
||||
if (it) {
|
||||
toast(R.string.toast_manual_available)
|
||||
dismissAllowingStateLoss()
|
||||
} else {
|
||||
toast(R.string.toast_manual_unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
this.lifecycleScope.launch {
|
||||
viewModel.purchaseStatus.collectLatest {
|
||||
if (it) {
|
||||
toast(R.string.toast_manual_available)
|
||||
dismissAllowingStateLoss()
|
||||
} else {
|
||||
toast(R.string.toast_manual_unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun inflateData() {
|
||||
binding.imgIcon.load(args.app.iconArtwork.url) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(32F))
|
||||
}
|
||||
|
||||
binding.txtLine1.text = args.app.displayName
|
||||
binding.txtLine2.text = args.app.packageName
|
||||
binding.txtLine3.text = ("${args.app.versionName} (${args.app.versionCode})")
|
||||
|
||||
binding.versionCodeLayout.hint = "${args.app.versionCode}"
|
||||
binding.versionCodeLayout.editText?.setText("${args.app.versionCode}")
|
||||
}
|
||||
|
||||
private fun attachActions() {
|
||||
binding.btnPrimary.setOnClickListener {
|
||||
val customVersionString = (binding.versionCodeInp.text).toString()
|
||||
if (customVersionString.isEmpty())
|
||||
binding.versionCodeInp.error = "Enter version code"
|
||||
else {
|
||||
viewModel.purchase(args.app, customVersionString.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnSecondary.setOnClickListener {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
@@ -24,11 +23,9 @@ import com.aurora.gplayapi.network.IHttpClient
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.helper.DownloadHelper
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.data.model.PlexusReport
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.model.Scores
|
||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.createPager
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.FavouriteDao
|
||||
@@ -36,20 +33,18 @@ import com.aurora.store.util.CertUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.PackageUtil.PACKAGE_NAME_GMS
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -68,8 +63,7 @@ class AppDetailsViewModel @Inject constructor(
|
||||
private val downloadHelper: DownloadHelper,
|
||||
private val favouriteDao: FavouriteDao,
|
||||
private val httpClient: IHttpClient,
|
||||
private val json: Json,
|
||||
private val exodusTrackers: JSONObject
|
||||
private val json: Json
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = AppDetailsViewModel::class.java.simpleName
|
||||
@@ -77,9 +71,8 @@ class AppDetailsViewModel @Inject constructor(
|
||||
private val _app = MutableStateFlow<App?>(App(""))
|
||||
val app = _app.asStateFlow()
|
||||
|
||||
private var reviewsNextPageUrl: String? = null
|
||||
private val _reviews = MutableStateFlow<PagingData<Review>>(PagingData.empty())
|
||||
val reviews = _reviews.asStateFlow()
|
||||
private val _suggestions = MutableStateFlow<List<App>>(emptyList())
|
||||
val suggestions = _suggestions.asStateFlow()
|
||||
|
||||
private val _userReview = MutableStateFlow<Review?>(null)
|
||||
val userReview = _userReview.asStateFlow()
|
||||
@@ -107,6 +100,27 @@ class AppDetailsViewModel @Inject constructor(
|
||||
list.find { d -> d.packageName == a?.packageName }
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
|
||||
// TODO: Save sessionId to downloads to monitor packageInstall progress
|
||||
val installProgress = callbackFlow<Float?> {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val callback = object : PackageInstaller.SessionCallback() {
|
||||
override fun onActiveChanged(p0: Int, p1: Boolean) {}
|
||||
override fun onBadgingChanged(p0: Int) {}
|
||||
override fun onCreated(p0: Int) {
|
||||
// trySend(0F)
|
||||
}
|
||||
override fun onFinished(p0: Int, p1: Boolean) {
|
||||
// trySend(null)
|
||||
}
|
||||
override fun onProgressChanged(p0: Int, p1: Float) {
|
||||
// trySend(p1)
|
||||
}
|
||||
}
|
||||
|
||||
packageInstaller.registerSessionCallback(callback)
|
||||
awaitClose { packageInstaller.unregisterSessionCallback(callback) }
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
|
||||
private val isExtendedUpdateEnabled: Boolean
|
||||
get() = Preferences.getBoolean(context, PREFERENCE_UPDATES_EXTENDED)
|
||||
|
||||
@@ -114,7 +128,7 @@ class AppDetailsViewModel @Inject constructor(
|
||||
get() = PackageUtil.isUpdatable(
|
||||
context,
|
||||
app.value!!.packageName,
|
||||
app.value!!.versionCode.toLong()
|
||||
app.value!!.versionCode
|
||||
)
|
||||
|
||||
private val hasValidCerts: Boolean
|
||||
@@ -128,15 +142,9 @@ class AppDetailsViewModel @Inject constructor(
|
||||
val hasValidUpdate: Boolean
|
||||
get() = (isUpdatable && hasValidCerts) || (isUpdatable && isExtendedUpdateEnabled)
|
||||
|
||||
val showSimilarApps: Boolean
|
||||
get() = Preferences.getBoolean(context, PREFERENCE_SIMILAR)
|
||||
|
||||
fun fetchAppDetails(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_favourite.value = favouriteDao.isFavourite(packageName)
|
||||
|
||||
_dataSafetyReport.value = webDataSafetyHelper.fetch(packageName)
|
||||
_app.value = appDetailsHelper.getAppByPackageName(packageName).copy(
|
||||
isInstalled = PackageUtil.isInstalled(context, packageName)
|
||||
)
|
||||
@@ -148,7 +156,9 @@ class AppDetailsViewModel @Inject constructor(
|
||||
// Only proceed if there was no error while fetching the app details
|
||||
if (throwable != null) return@invokeOnCompletion
|
||||
|
||||
fetchReviews()
|
||||
fetchFavourite(packageName)
|
||||
fetchDataSafetyReport(packageName)
|
||||
fetchSuggestions()
|
||||
fetchExodusPrivacyReport(packageName)
|
||||
if (app.value!!.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
fetchPlexusReport(packageName)
|
||||
@@ -156,48 +166,11 @@ class AppDetailsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchReviews(filter: Review.Filter = Review.Filter.ALL) {
|
||||
reviewsNextPageUrl = null
|
||||
|
||||
createPager {
|
||||
val reviewsCluster = reviewsNextPageUrl?.let { nextPageUrl ->
|
||||
reviewsHelper.next(nextPageUrl)
|
||||
} ?: reviewsHelper.getReviews(app.value!!.packageName, filter)
|
||||
|
||||
reviewsNextPageUrl = reviewsCluster.nextPageUrl
|
||||
reviewsCluster.reviewList
|
||||
}.flow.distinctUntilChanged()
|
||||
.cachedIn(viewModelScope)
|
||||
.onEach { _reviews.value = it }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun fetchExodusPrivacyReport(packageName: String) {
|
||||
fun updateTestingProgramStatus(packageName: String, subscribe: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_exodusReport.value = getLatestExodusReport(packageName)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch privacy report", exception)
|
||||
_exodusReport.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchPlexusReport(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_plexusScores.value = getPlexusReport(packageName)?.report?.scores
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch compatibility report", exception)
|
||||
_plexusScores.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchTestingProgramStatus(packageName: String, subscribe: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_testingProgramStatus.value = appDetailsHelper.testingProgram(packageName, subscribe)
|
||||
_testingProgramStatus.value =
|
||||
appDetailsHelper.testingProgram(packageName, subscribe)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch testing program status", exception)
|
||||
}
|
||||
@@ -272,24 +245,50 @@ class AppDetailsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun getExodusTrackersFromReport(): List<ExodusTracker> {
|
||||
val trackerObjects = exodusReport.value!!.trackers.map {
|
||||
exodusTrackers.getJSONObject(it.toString())
|
||||
}.toList()
|
||||
private fun fetchFavourite(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_favourite.value = favouriteDao.isFavourite(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
return trackerObjects.map {
|
||||
ExodusTracker(
|
||||
id = it.getInt("id"),
|
||||
name = it.getString("name"),
|
||||
url = it.getString("website"),
|
||||
signature = it.getString("code_signature"),
|
||||
date = it.getString("creation_date"),
|
||||
description = it.getString("description"),
|
||||
networkSignature = it.getString("network_signature"),
|
||||
documentation = listOf(it.getString("documentation")),
|
||||
categories = listOf(it.getString("categories"))
|
||||
)
|
||||
}.toList()
|
||||
private fun fetchDataSafetyReport(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_dataSafetyReport.value = webDataSafetyHelper.fetch(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchSuggestions() {
|
||||
// Bail out if we go no suggestions to offer
|
||||
if (app.value!!.detailsStreamUrl.isNullOrBlank()) return
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val streamBundle = appDetailsHelper.getDetailsStream(app.value!!.detailsStreamUrl!!)
|
||||
_suggestions.value = streamBundle.streamClusters.values
|
||||
.flatMap { it.clusterAppList }
|
||||
.distinctBy { it.packageName }
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchExodusPrivacyReport(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_exodusReport.value = getLatestExodusReport(packageName)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch privacy report", exception)
|
||||
_exodusReport.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchPlexusReport(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_plexusScores.value = getPlexusReport(packageName)?.report?.scores
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch compatibility report", exception)
|
||||
_plexusScores.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLatestExodusReport(packageName: String): Report? {
|
||||
@@ -305,12 +304,6 @@ class AppDetailsViewModel @Inject constructor(
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private fun getPlexusReport(packageName: String): PlexusReport? {
|
||||
val url = "${Constants.PLEXUS_API_URL}/${packageName}/?scores=true"
|
||||
val playResponse = httpClient.get(url, emptyMap())
|
||||
return json.decodeFromString<PlexusReport>(String(playResponse.responseBytes))
|
||||
}
|
||||
|
||||
private fun parseExodusResponse(response: String, packageName: String): List<Report> {
|
||||
try {
|
||||
val jsonObject = JSONObject(response)
|
||||
@@ -322,4 +315,10 @@ class AppDetailsViewModel @Inject constructor(
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlexusReport(packageName: String): PlexusReport? {
|
||||
val url = "${Constants.PLEXUS_API_URL}/${packageName}/?scores=true"
|
||||
val playResponse = httpClient.get(url, emptyMap())
|
||||
return json.decodeFromString<PlexusReport>(String(playResponse.responseBytes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,56 @@
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.data.model.Report
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DetailsExodusViewModel @Inject constructor(val exodusTrackers: JSONObject) : ViewModel()
|
||||
@HiltViewModel(assistedFactory = DetailsExodusViewModel.Factory::class)
|
||||
class DetailsExodusViewModel @AssistedInject constructor(
|
||||
@Assisted private val exodusReport: Report,
|
||||
private val exodusTrackers: JSONObject
|
||||
) : ViewModel() {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(exodusReport: Report): DetailsExodusViewModel
|
||||
}
|
||||
|
||||
private val _trackers = MutableStateFlow<List<ExodusTracker>>(emptyList())
|
||||
val trackers = _trackers.asStateFlow()
|
||||
|
||||
init {
|
||||
getExodusTrackersFromReport()
|
||||
}
|
||||
|
||||
fun getExodusTrackersFromReport() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val trackerObjects = exodusReport.trackers.map {
|
||||
exodusTrackers.getJSONObject(it.toString())
|
||||
}.toList()
|
||||
|
||||
_trackers.value = trackerObjects.map {
|
||||
ExodusTracker(
|
||||
id = it.getInt("id"),
|
||||
name = it.getString("name"),
|
||||
url = it.getString("website"),
|
||||
signature = it.getString("code_signature"),
|
||||
date = it.getString("creation_date"),
|
||||
description = it.getString("description"),
|
||||
networkSignature = it.getString("network_signature"),
|
||||
documentation = listOf(it.getString("documentation")),
|
||||
categories = listOf(it.getString("categories"))
|
||||
)
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 The Calyx Institute
|
||||
* SPDX-FileCopyrightText: 2024 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import android.util.Log
|
||||
@@ -5,36 +11,42 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DetailsMoreViewModel @Inject constructor(
|
||||
@HiltViewModel(assistedFactory = DetailsMoreViewModel.Factory::class)
|
||||
class DetailsMoreViewModel @AssistedInject constructor(
|
||||
@Assisted private val dependencies: List<String>,
|
||||
private val appDetailsHelper: AppDetailsHelper
|
||||
) : ViewModel() {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(dependencies: List<String>): DetailsMoreViewModel
|
||||
}
|
||||
|
||||
private val TAG = DetailsMoreViewModel::class.java.simpleName
|
||||
|
||||
private val dependantAppsStash = mutableMapOf<String, List<App>>()
|
||||
private val _dependentApps = MutableSharedFlow<List<App>>()
|
||||
val dependentApps = _dependentApps.asSharedFlow()
|
||||
private val _dependentApps = MutableStateFlow<List<App>?>(emptyList())
|
||||
val dependentApps = _dependentApps.asStateFlow()
|
||||
|
||||
fun fetchDependentApps(app: App) {
|
||||
init {
|
||||
fetchDependencies()
|
||||
}
|
||||
|
||||
private fun fetchDependencies() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val dependantApps = dependantAppsStash.getOrPut(app.packageName) {
|
||||
appDetailsHelper.getAppByPackageName(app.dependencies.dependentPackages)
|
||||
}
|
||||
|
||||
_dependentApps.emit(dependantApps)
|
||||
_dependentApps.value = appDetailsHelper.getAppByPackageName(dependencies)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch dependencies", exception)
|
||||
dependantAppsStash[app.packageName] = emptyList()
|
||||
_dependentApps.emit(emptyList())
|
||||
_dependentApps.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 AuroraOSS
|
||||
* SPDX-FileCopyrightText: 2024-2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.review
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.createPager
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@HiltViewModel(assistedFactory = DetailsReviewViewModel.Factory::class)
|
||||
class DetailsReviewViewModel @AssistedInject constructor(
|
||||
@Assisted private val packageName: String,
|
||||
private val reviewsHelper: ReviewsHelper
|
||||
) : ViewModel() {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(packageName: String): DetailsReviewViewModel
|
||||
}
|
||||
|
||||
private val TAG = DetailsReviewViewModel::class.java.simpleName
|
||||
|
||||
private var reviewsNextPageUrl: String? = null
|
||||
private val _reviews = MutableStateFlow<PagingData<Review>>(PagingData.empty())
|
||||
val reviews = _reviews.asStateFlow()
|
||||
|
||||
init {
|
||||
fetchReviews()
|
||||
}
|
||||
|
||||
fun fetchReviews(filter: Review.Filter = Review.Filter.ALL) {
|
||||
reviewsNextPageUrl = null
|
||||
|
||||
createPager { page ->
|
||||
try {
|
||||
when (page) {
|
||||
1 -> reviewsHelper.getReviews(packageName, filter).also {
|
||||
reviewsNextPageUrl = it.nextPageUrl
|
||||
}.reviewList
|
||||
|
||||
else -> {
|
||||
if (!reviewsNextPageUrl.isNullOrBlank()) {
|
||||
reviewsHelper.next(reviewsNextPageUrl!!).also {
|
||||
reviewsNextPageUrl = it.nextPageUrl
|
||||
}.reviewList
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception)
|
||||
emptyList()
|
||||
}
|
||||
}.flow.distinctUntilChanged()
|
||||
.cachedIn(viewModelScope)
|
||||
.onEach { _reviews.value = it }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.review
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.ReviewCluster
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ReviewViewModel @Inject constructor(
|
||||
private val reviewsHelper: ReviewsHelper
|
||||
) : ViewModel() {
|
||||
val TAG = javaClass.simpleName
|
||||
|
||||
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
|
||||
|
||||
private lateinit var reviewsCluster: ReviewCluster
|
||||
|
||||
fun fetchReview(packageName: String, filter: Review.Filter) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to fetch reviews", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun next(nextReviewPageUrl: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val currentCluster = reviewsCluster
|
||||
val nextReviewCluster = reviewsHelper.next(nextReviewPageUrl)
|
||||
|
||||
reviewsCluster = currentCluster.copy(
|
||||
nextPageUrl = nextReviewCluster.nextPageUrl,
|
||||
reviewList = currentCluster.reviewList + nextReviewCluster.reviewList
|
||||
)
|
||||
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to fetch next reviews $nextReviewPageUrl", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.aurora.store.viewmodel.sheets
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ManualDownloadViewModel @Inject constructor(
|
||||
private val purchaseHelper: PurchaseHelper
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = ManualDownloadViewModel::class.java.simpleName
|
||||
|
||||
private val _purchaseStatus = MutableSharedFlow<Boolean>()
|
||||
val purchaseStatus = _purchaseStatus.asSharedFlow()
|
||||
|
||||
fun purchase(app: App, customVersion: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val files = purchaseHelper.purchase(app.packageName, customVersion, app.offerType)
|
||||
if (files.isNotEmpty()) {
|
||||
AuroraApp.events.send(
|
||||
BusEvent.ManualDownload(app.packageName, customVersion)
|
||||
)
|
||||
}
|
||||
_purchaseStatus.emit(files.isNotEmpty())
|
||||
} catch (exception: Exception) {
|
||||
_purchaseStatus.emit(false)
|
||||
Log.e(TAG, "Failed to find version: $customVersion", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
app/src/main/res/drawable/ic_experiment.xml
Normal file
14
app/src/main/res/drawable/ic_experiment.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M200,840Q149,840 127.5,794.5Q106,749 138,710L360,440L360,200L320,200Q303,200 291.5,188.5Q280,177 280,160Q280,143 291.5,131.5Q303,120 320,120L640,120Q657,120 668.5,131.5Q680,143 680,160Q680,177 668.5,188.5Q657,200 640,200L600,200L600,440L822,710Q854,749 832.5,794.5Q811,840 760,840L200,840ZM280,720L680,720L544,560L416,560L280,720ZM200,760L760,760L520,468L520,200L440,200L440,468L200,760ZM480,480L480,480L480,480L480,480L480,480L480,480Z"/>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_suggestions.xml
Normal file
14
app/src/main/res/drawable/ic_suggestions.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,880Q447,880 423.5,856.5Q400,833 400,800L560,800Q560,833 536.5,856.5Q513,880 480,880ZM320,760L320,680L640,680L640,760L320,760ZM330,640Q261,599 220.5,530Q180,461 180,380Q180,255 267.5,167.5Q355,80 480,80Q605,80 692.5,167.5Q780,255 780,380Q780,461 739.5,530Q699,599 630,640L330,640ZM354,560L606,560Q651,528 675.5,481Q700,434 700,380Q700,288 636,224Q572,160 480,160Q388,160 324,224Q260,288 260,380Q260,434 284.5,481Q309,528 354,560ZM480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Q480,560 480,560Z"/>
|
||||
</vector>
|
||||
@@ -56,74 +56,57 @@
|
||||
android:id="@+id/layout_details_app"
|
||||
layout="@layout/layout_details_app" />
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<RelativeLayout
|
||||
<include
|
||||
android:id="@+id/layout_detail_description"
|
||||
layout="@layout/layout_details_description" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_review"
|
||||
layout="@layout/layout_details_review" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_beta"
|
||||
layout="@layout/layout_details_beta" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler_stream"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="320dp">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_app" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_detail_description"
|
||||
layout="@layout/layout_details_description" />
|
||||
<include
|
||||
android:id="@+id/layout_details_compatibility"
|
||||
layout="@layout/layout_details_compatibility" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_review"
|
||||
layout="@layout/layout_details_review" />
|
||||
<include
|
||||
android:id="@+id/layout_details_permissions"
|
||||
layout="@layout/layout_details_permissions" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_beta"
|
||||
layout="@layout/layout_details_beta" />
|
||||
<include
|
||||
android:id="@+id/layout_details_data_safety"
|
||||
layout="@layout/layout_details_data_safety" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler_stream"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_app" />
|
||||
<include
|
||||
android:id="@+id/layout_details_privacy"
|
||||
layout="@layout/layout_details_privacy" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_compatibility"
|
||||
layout="@layout/layout_details_compatibility" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_permissions"
|
||||
layout="@layout/layout_details_permissions" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_data_safety"
|
||||
layout="@layout/layout_details_data_safety" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_privacy"
|
||||
layout="@layout/layout_details_privacy" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_dev"
|
||||
layout="@layout/layout_details_dev" />
|
||||
</LinearLayout>
|
||||
</ViewFlipper>
|
||||
<include
|
||||
android:id="@+id/layout_details_dev"
|
||||
layout="@layout/layout_details_dev" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,106 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.details.DetailsMoreFragment">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:showDividers="middle">
|
||||
|
||||
<com.aurora.store.view.custom.layouts.ActionHeaderLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
app:headerTitle="@string/details_description" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
<com.aurora.store.view.custom.layouts.ActionHeaderLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
app:headerTitle="@string/details_dependencies" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler_dependency"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="true"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:orientation="horizontal"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/view_app_dependent" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler_more"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="true"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:orientation="vertical"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/view_file" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
@@ -1,43 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.details.ScreenshotFragment">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:translationZ="1dp"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_screenshot" />
|
||||
</LinearLayout>
|
||||
@@ -1,40 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ViewStub
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/header_view" />
|
||||
|
||||
<ViewStub
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/recycler_view_01" />
|
||||
|
||||
<ViewStub
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/recycler_view_02" />
|
||||
</LinearLayout>
|
||||
@@ -1,140 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_large"
|
||||
android:showDividers="middle">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="@dimen/icon_size_category"
|
||||
android:layout_height="@dimen/icon_size_category"
|
||||
android:layout_centerVertical="true"
|
||||
tools:src="@drawable/bg_placeholder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginBottom="@dimen/margin_normal"
|
||||
android:layout_toEndOf="@id/img_icon"
|
||||
android:maxLines="1"
|
||||
android:text="@string/title_manual_download"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
tools:text="App Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_line1"
|
||||
android:layout_alignStart="@id/txt_line1"
|
||||
android:layout_alignEnd="@id/txt_line1"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
android:textColor="?colorAccent"
|
||||
tools:text="Package Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_line2"
|
||||
android:layout_alignStart="@id/txt_line1"
|
||||
android:layout_alignEnd="@id/txt_line1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line3"
|
||||
tools:text="Base version" />
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/version_code_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:helperText="@string/manual_download_hint">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/version_code_inp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_secondary"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog.Flush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_cancel" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_primary"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog.Flush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_install" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -1,59 +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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/padding_small"
|
||||
android:textIsSelectable="true">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Line1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_title"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Line2" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_subtitle"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line3"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Line3" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,48 +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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/padding_xsmall"
|
||||
android:paddingStart="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_xsmall">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
tools:text="File Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/line2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/line1"
|
||||
android:layout_alignStart="@id/line1"
|
||||
android:layout_alignEnd="@id/line1"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
tools:text="File Size" />
|
||||
</RelativeLayout>
|
||||
@@ -1,81 +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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/padding_xsmall">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="@dimen/icon_size_small"
|
||||
android:layout_height="@dimen/icon_size_small"
|
||||
android:layout_marginEnd="@dimen/margin_small" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_author"
|
||||
style="@style/TextAppearance.Aurora.Line1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/img"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="14sp"
|
||||
tools:text="Author Name" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_time"
|
||||
style="@style/TextAppearance.Aurora.Line3"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_author"
|
||||
android:layout_alignStart="@id/txt_author"
|
||||
android:layout_alignEnd="@id/txt_author"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Date" />
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/rating"
|
||||
style="@style/Widget.AppCompat.RatingBar.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_time"
|
||||
android:layout_alignStart="@id/txt_author"
|
||||
android:numStars="5" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_comment"
|
||||
style="@style/TextAppearance.Aurora.Line2"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/img"
|
||||
android:layout_alignStart="@id/txt_author"
|
||||
android:layout_marginTop="@dimen/margin_xsmall"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="10"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Comment" />
|
||||
</RelativeLayout>
|
||||
@@ -1,29 +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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="@dimen/screenshot_width"
|
||||
android:layout_height="@dimen/screenshot_height"
|
||||
android:adjustViewBounds="true" />
|
||||
</RelativeLayout>
|
||||
@@ -1,28 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
@@ -1,29 +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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="@dimen/screenshot_width_mini"
|
||||
android:layout_height="@dimen/screenshot_height_mini"
|
||||
android:adjustViewBounds="true" />
|
||||
</RelativeLayout>
|
||||
@@ -32,9 +32,6 @@
|
||||
<action
|
||||
android:id="@+id/action_global_expandedStreamBrowseFragment"
|
||||
app:destination="@id/expandedStreamBrowseFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_screenshotFragment"
|
||||
app:destination="@id/screenshotFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_devProfileFragment"
|
||||
app:destination="@id/devProfileFragment" />
|
||||
@@ -165,18 +162,11 @@
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_devAppsFragment"
|
||||
app:destination="@id/devAppsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_detailsMoreFragment"
|
||||
app:destination="@id/detailsMoreFragment" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_detailsReviewFragment"
|
||||
app:destination="@id/detailsReviewFragment" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_detailsExodusFragment"
|
||||
app:destination="@id/detailsExodusFragment" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_manualDownloadSheet"
|
||||
app:destination="@id/manualDownloadSheet" />
|
||||
<argument
|
||||
android:name="app"
|
||||
android:defaultValue="@null"
|
||||
app:argType="com.aurora.gplayapi.data.models.App"
|
||||
app:nullable="true" />
|
||||
<action
|
||||
android:id="@+id/action_appDetailsFragment_to_permissionBottomSheet"
|
||||
app:destination="@id/permissionBottomSheet" />
|
||||
@@ -209,17 +199,6 @@
|
||||
android:name="expandedStreamUrl"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/screenshotFragment"
|
||||
android:name="com.aurora.store.view.ui.details.ScreenshotFragment"
|
||||
tools:layout="@layout/fragment_screenshot">
|
||||
<argument
|
||||
android:name="position"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="arrayOfArtwork"
|
||||
app:argType="com.aurora.gplayapi.data.models.Artwork[]" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/devProfileFragment"
|
||||
android:name="com.aurora.store.view.ui.details.DevProfileFragment"
|
||||
@@ -253,36 +232,6 @@
|
||||
android:name="developerName"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/detailsMoreFragment"
|
||||
android:name="com.aurora.store.view.ui.details.DetailsMoreFragment"
|
||||
tools:layout="@layout/fragment_details_more">
|
||||
<argument
|
||||
android:name="app"
|
||||
app:argType="com.aurora.gplayapi.data.models.App" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/detailsReviewFragment"
|
||||
android:name="com.aurora.store.view.ui.details.DetailsReviewFragment"
|
||||
tools:layout="@layout/fragment_details_review">
|
||||
<argument
|
||||
android:name="displayName"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="packageName"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/detailsExodusFragment"
|
||||
android:name="com.aurora.store.view.ui.details.DetailsExodusFragment"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar">
|
||||
<argument
|
||||
android:name="displayName"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="report"
|
||||
app:argType="com.aurora.store.data.model.Report" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/splashFragment"
|
||||
android:name="com.aurora.store.view.ui.splash.SplashFragment"
|
||||
@@ -397,15 +346,6 @@
|
||||
android:name="com.aurora.store.view.ui.sheets.FilterSheet"
|
||||
android:label="FilterSheet"
|
||||
tools:layout="@layout/sheet_filter" />
|
||||
<dialog
|
||||
android:id="@+id/manualDownloadSheet"
|
||||
android:name="com.aurora.store.view.ui.sheets.ManualDownloadSheet"
|
||||
android:label="ManualDownloadSheet"
|
||||
tools:layout="@layout/sheet_manual_download">
|
||||
<argument
|
||||
android:name="app"
|
||||
app:argType="com.aurora.gplayapi.data.models.App" />
|
||||
</dialog>
|
||||
<dialog
|
||||
android:id="@+id/permissionBottomSheet"
|
||||
android:name="com.aurora.store.view.ui.sheets.PermissionBottomSheet"
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="title_library">المكتبة</string>
|
||||
<string name="insecure_anonymous_apply">تأكد من إعادة تسجيل الدخول وإعادة تشغيل التطبيق لتطبيق التغييرات.</string>
|
||||
<string name="pref_network_title">الشبكات</string>
|
||||
<string name="pref_ui_similar_apps_desc">عرض مجموعات متشابهة وذات صلة في صفحة تفاصيل التطبيق</string>
|
||||
<string name="pref_ui_similar_apps">تطبيقات مشابهة و ذات صلة</string>
|
||||
<string name="pref_ui_no_for_you_desc">عرض صفحات لك على الشاشة الرئيسية</string>
|
||||
<string name="pref_ui_no_for_you">صفحات لك</string>
|
||||
|
||||
@@ -180,7 +180,6 @@
|
||||
<string name="pref_install_mode_am">Instalador AM</string>
|
||||
<string name="pref_install_mode_summary">Seleiciona\'l métodu d\'instalación de los archivos APK</string>
|
||||
<string name="pref_ui_similar_apps">Aplicaciones asemeyaes y rellacionaes</string>
|
||||
<string name="pref_ui_similar_apps_desc">Amuesa les aplicaciones asemeyaes y rellacionaes nes páxines de detalles de les aplicaciones</string>
|
||||
<string name="tab_for_you">Pa ti</string>
|
||||
<string name="title_installed">Instalóse</string>
|
||||
<string name="title_installer">App Installer</string>
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
<string name="pref_ui_layout">Tərtibat</string>
|
||||
<string name="pref_ui_layout_tab">İlkin səhifəni seç</string>
|
||||
<string name="pref_ui_similar_apps">Oxşar və əlaqəli tətbiqlər</string>
|
||||
<string name="pref_ui_similar_apps_desc">Tətbiq təfərrüatları səhifəsində oxşar və əlaqəli qrupları göstər</string>
|
||||
<string name="purchase_failed">Endirmə Alınmadı</string>
|
||||
<string name="purchase_invalid">Tətbiq satın alınmayıb</string>
|
||||
<string name="purchase_unsupported">Tətbiq dəstəklənmir</string>
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
<string name="pref_network_proxy_url">URL проксі</string>
|
||||
<string name="pref_network_proxy_url_message">Увядзіце сапраўдны URL для праходжання ўсяго трафіку праз проксі.</string>
|
||||
<string name="pref_ui_similar_apps">Падобнае</string>
|
||||
<string name="pref_ui_similar_apps_desc">Паказваць падобныя ці звязаныя кластары на старонцы праграмы</string>
|
||||
<string name="pref_aurora_only_desc">Не правяраць наяўнасць абнаўленняў для праграм, усталяваных з крыніц па-за межамі крамы Aurora</string>
|
||||
<string name="purchase_failed">Памылка спампоўкі</string>
|
||||
<string name="purchase_no_file">Не ўдалося атрымаць файл</string>
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
<string name="pref_install_mode_services">Услуги на Aurora</string>
|
||||
<string name="pref_install_mode_summary">Изберете режим на инсталиране на APK</string>
|
||||
<string name="pref_ui_no_for_you_desc">Показва страници за вас на началния екран</string>
|
||||
<string name="pref_ui_similar_apps_desc">Показва подобни и свързани клъстери на страницата с подробности за приложението</string>
|
||||
<string name="purchase_failed">Изтеглянето се провали</string>
|
||||
<string name="purchase_unsupported">Приложението не се поддържа</string>
|
||||
<string name="purchase_not_found">Приложението не е намерено</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="pref_ui_similar_apps">Aplicacions similars i relacionades</string>
|
||||
<string name="purchase_unsupported">Aplicació no compatible</string>
|
||||
<string name="purchase_not_found">No s\'ha trobat l\'aplicació</string>
|
||||
<string name="pref_ui_similar_apps_desc">Mostrar grups similars i relacionats a la pàgina detalls de l\'aplicació</string>
|
||||
<string name="purchase_failed">Error en descarregar</string>
|
||||
<string name="title_blacklist_manager">Gestor de llistes negres</string>
|
||||
<string name="pref_ui_no_for_you_desc">Visualitzar les Pàgines per a tu a la pantalla d\'inici</string>
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
<string name="purchase_unsupported">Aplikace není kompatibilní</string>
|
||||
<string name="purchase_invalid">Aplikace nebyla zakoupena</string>
|
||||
<string name="purchase_failed">Stažení se nezdařilo</string>
|
||||
<string name="pref_ui_similar_apps_desc">Zobrazit podobné a související skupiny na stránce s detaily aplikace</string>
|
||||
<string name="pref_ui_similar_apps">Podobné a související aplikace</string>
|
||||
<string name="pref_ui_no_for_you_desc">Zobrazit stránky pro vás na domovské obrazovce</string>
|
||||
<string name="pref_ui_no_for_you">Stránky pro vás</string>
|
||||
|
||||
@@ -105,7 +105,6 @@
|
||||
<string name="pref_install_mode_am">AM-installationsprogram</string>
|
||||
<string name="pref_ui_layout">Layout</string>
|
||||
<string name="purchase_not_found">Appen blev ikke fundet</string>
|
||||
<string name="pref_ui_similar_apps_desc">Vis lignende og relaterede samlinger på appens detaljeside</string>
|
||||
<string name="spoof_apply">Sørg for at logge ind igen for at anvende spoofen</string>
|
||||
<string name="title_manual_download">Manuel download</string>
|
||||
<string name="toast_apk_whitelisted">Hvidlistet</string>
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="toast_purchase_blocked">App-Käufe sind für anonyme Konten nicht verfügbar.</string>
|
||||
<string name="insecure_anonymous_apply">Bitte neu anmelden und die App neu starten, um die Änderungen zu übernehmen.</string>
|
||||
<string name="pref_network_title">Netzwerk</string>
|
||||
<string name="pref_ui_similar_apps_desc">Ähnliche und verwandte Apps in der App-Detailansicht anzeigen</string>
|
||||
<string name="pref_ui_similar_apps">Ähnliche Apps</string>
|
||||
<string name="pref_ui_no_for_you_desc">„Für dich“-Tab auf der Startseite anzeigen</string>
|
||||
<string name="pref_ui_no_for_you">„Für dich“-Tab</string>
|
||||
|
||||
@@ -168,7 +168,6 @@
|
||||
<string name="pref_ui_title">Προσαρμογή</string>
|
||||
<string name="pref_ui_no_for_you_desc">Εμφάνιση σελίδων για εσάς στην αρχική οθόνη</string>
|
||||
<string name="purchase_unsupported">Η εφαρμογή δεν υποστηρίζεται</string>
|
||||
<string name="pref_ui_similar_apps_desc">Εμφάνιση παρόμοιων και σχετικών ομάδων στη σελίδα λεπτομερειών εφαρμογής</string>
|
||||
<string name="purchase_session_expired">Η συνεδρία έληξε, συνδεθείτε ξανά για να λάβετε νέα.</string>
|
||||
<string name="tab_top_paid">Κορυφαίες επί πληρωμή</string>
|
||||
<string name="title_device">Συσκευή</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="download_eta_min">ankoraŭ %1$d m %2$d s</string>
|
||||
<string name="pref_ui_title">Agordo</string>
|
||||
<string name="pref_ui_no_for_you">\"Por vi\" paĝoj</string>
|
||||
<string name="pref_ui_similar_apps_desc">Vidigi similajn kaj rilatajn apojn sur la paĝo de la apon detaloj</string>
|
||||
<string name="pref_install_mode_shizuku">Shizuku instalilo</string>
|
||||
<string name="pref_install_mode_am">AM instalilo</string>
|
||||
<string name="action_request_analysis">Peti novan analizon</string>
|
||||
|
||||
@@ -211,7 +211,6 @@
|
||||
<string name="toast_manual_available">Felicidades, el código de la versión solicitada está disponible, descargándolo ahora.</string>
|
||||
<string name="toast_manual_unavailable">El código de versión que estás solicitando no está disponible.</string>
|
||||
<string name="title_manual_download">Descarga manual</string>
|
||||
<string name="pref_ui_similar_apps_desc">Mostrar grupos similares y relacionados en la página de detalles de la aplicación</string>
|
||||
<string name="pref_ui_similar_apps">Aplicaciones similares y relacionadas</string>
|
||||
<string name="pref_ui_no_for_you_desc">Mostrar las páginas Para ti en la pantalla de inicio</string>
|
||||
<string name="pref_ui_no_for_you">Páginas para ti</string>
|
||||
|
||||
@@ -201,7 +201,6 @@
|
||||
<string name="notification_channel_updates">Uuenduste märguanded</string>
|
||||
<string name="permissions_denied">Vajalikud load on keelatud. Toimingu jätkamiseks luba need</string>
|
||||
<string name="pref_updates_auto">Automaatsed uuendused</string>
|
||||
<string name="pref_ui_similar_apps_desc">Kuva rakenduse üksikasjade lehel sarnaseid ja seotud rakendusi</string>
|
||||
<string name="title_manual_download">Käsitsi allalaadimine</string>
|
||||
<string name="toast_clipboard_copied">Kopeeriti lõikelauale</string>
|
||||
<string name="toast_manual_available">Õnnitlused, taotletud versioonikood on saadaval, laadime alla.</string>
|
||||
|
||||
@@ -216,7 +216,6 @@
|
||||
<string name="toast_manual_unavailable">Eskatzen ari zaren bertsio kodea ez dago erabilgarri.</string>
|
||||
<string name="title_manual_download">Eskuzko deskargatzea</string>
|
||||
<string name="insecure_anonymous_apply">Ziurtatu berriro hasten duzula saioa eta berrabiarazi aplikazioa aldaketak aplikatzeko.</string>
|
||||
<string name="pref_ui_similar_apps_desc">Erakutsi antzeko taldeak eta erlazionatuak aplikazioaren xehetasunen orrian</string>
|
||||
<string name="pref_ui_similar_apps">Antzeko aplikazioak eta erlazionatuak</string>
|
||||
<string name="pref_ui_no_for_you_desc">Erakutsi zuretzako orriak hasierako pantailan</string>
|
||||
<string name="pref_ui_no_for_you">Zuretzako orrialdeak</string>
|
||||
|
||||
@@ -223,7 +223,6 @@
|
||||
<string name="onboarding_permission_installer_desc">اجازه نصب برنامهها از فروشگاه آرورا</string>
|
||||
<string name="pref_install_delete_title">حذف APK پس از نصب</string>
|
||||
<string name="pref_install_mode_am">نصب کننده AM.</string>
|
||||
<string name="pref_ui_similar_apps_desc">نمایش خوشه های مشابه و مرتبط در صفحه جزئیات برنامه</string>
|
||||
<string name="spoof_apply">اطمینان حاصل کنید که برای اعمال پارامتر جعلی دوباره وارد سیستم شوید</string>
|
||||
<string name="tab_editor_choice">انتخاب سردبیران</string>
|
||||
<string name="title_no_network">بدون شبکه</string>
|
||||
|
||||
@@ -187,7 +187,6 @@
|
||||
<string name="insecure_anonymous_apply">Varmista, että kirjaudut uudelleen ja käynnistät sovelluksen uudelleen ottaaksesi muutokset käyttöön.</string>
|
||||
<string name="spoof_apply">Varmista, että kirjaudut uudelleen ottaaksesi huijauksen käyttöösi</string>
|
||||
<string name="purchase_session_expired">Sessio päättyi, kirjaudu uudelleen uuden session aloittamiseksi.</string>
|
||||
<string name="pref_ui_similar_apps_desc">Näytä sovelluksen lisätietosivulla sen kanssa samankaltaisia ja siihen liittyviä sovelluksia</string>
|
||||
<string name="pref_ui_no_for_you_desc">Näytä Sinulle räätälöity-sivut kotinäytöllä</string>
|
||||
<string name="pref_ui_layout_tab">Valitse oletusvälilehti</string>
|
||||
<string name="pref_ui_layout">Ulkoasu</string>
|
||||
|
||||
@@ -209,7 +209,6 @@
|
||||
<string name="insecure_anonymous_apply">Reconnectez-vous et redémarrez l’appli pour appliquer les changements.</string>
|
||||
<string name="pref_network_title">Mise en réseau</string>
|
||||
<string name="pref_ui_no_for_you">Pages « Pour vous »</string>
|
||||
<string name="pref_ui_similar_apps_desc">Afficher les grappes semblables et connexes sur la page des détails de l’appli</string>
|
||||
<string name="pref_ui_similar_apps">Applis semblables et connexes</string>
|
||||
<string name="pref_ui_no_for_you_desc">Afficher les pages « Pour vous » sur l’écran d’accueil</string>
|
||||
<string name="pref_ui_layout_tab">Choisir l’onglet par défaut</string>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<string name="purchase_unsupported">Aplicación non soportada</string>
|
||||
<string name="purchase_invalid">Aplicación non mercada</string>
|
||||
<string name="purchase_failed">Descarga fallida</string>
|
||||
<string name="pref_ui_similar_apps_desc">Amosar grupos similares e relacionados na páxina de detalles da aplicación</string>
|
||||
<string name="pref_ui_similar_apps">Aplicacións semellantes e relacionadas</string>
|
||||
<string name="pref_ui_no_for_you_desc">Mostrar Suxestións Personalizadas na pantalla de inicio</string>
|
||||
<string name="pref_ui_no_for_you">Suxestións personalizadas</string>
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
<string name="pref_install_mode_session">מתקין מערכת</string>
|
||||
<string name="pref_ui_layout_tab">בחר עמוד ברירת מחדל</string>
|
||||
<string name="pref_ui_no_for_you_desc">הצגת עמודים בשבילך במסך הבית</string>
|
||||
<string name="pref_ui_similar_apps_desc">יישומים דומים וקשורים במסך פרטי היישום</string>
|
||||
<string name="title_spoof_manager">מנהל הזיוף</string>
|
||||
<string name="details_beta_description">אתה תראה תכונות חדשות ובאגים לפני כולם. תן משוב למפתחים כדי לעזור להם להשתפר.</string>
|
||||
<string name="device_miui_extra">אפשרי לבחור מתקין נתיב, אבל לא תוכל להתקין התקנות מאוחדות (מפוצלות - split), אז הבחירה בידך.</string>
|
||||
|
||||
@@ -218,7 +218,6 @@
|
||||
<string name="pref_install_mode_root">रूट इंस्टॉलर</string>
|
||||
<string name="pref_install_mode_services">ऑरोरा सेवाएं (अस्वीकृत)</string>
|
||||
<string name="purchase_session_expired">सत्र समाप्त हो गया, नया सत्र प्राप्त करने के लिए पुनः लॉगिन करें।</string>
|
||||
<string name="pref_ui_similar_apps_desc">ऐप विवरण पृष्ठ पर समान और संबंधित समूह प्रदर्शित करें</string>
|
||||
<string name="spoof_apply">सुनिश्चित करें कि आप छलावरण लागू करने के लिए पुनः लॉगिन करें</string>
|
||||
<string name="purchase_unsupported">ऐप समर्थित नहीं है</string>
|
||||
<string name="toast_apk_whitelisted">श्वेतसूचीबद्ध</string>
|
||||
|
||||
@@ -211,7 +211,6 @@
|
||||
<string name="toast_manual_available">Čestitamo, traženi kod verzije je dostupan, preuzima se.</string>
|
||||
<string name="toast_manual_unavailable">Zatraženi kod verzije nije dostupan.</string>
|
||||
<string name="title_manual_download">Ručno preuzimanje</string>
|
||||
<string name="pref_ui_similar_apps_desc">Prikaži slične i srodne skupove podataka na stranici detalja aplikacije</string>
|
||||
<string name="pref_ui_similar_apps">Slične i srodne aplikacije</string>
|
||||
<string name="pref_ui_no_for_you_desc">Prikaži „Za tvoje stranice” na početnom ekranu</string>
|
||||
<string name="pref_ui_no_for_you">Za tvoje stranice</string>
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="action_installations">Telepítések</string>
|
||||
<string name="action_disable">Tiltás</string>
|
||||
<string name="action_clear">Törlés</string>
|
||||
<string name="pref_ui_similar_apps_desc">Hasonló és releváns csoportok megjelenítése a részletek lapon</string>
|
||||
<string name="pref_ui_similar_apps">Hasonló és releváns alkalmazások</string>
|
||||
<string name="pref_ui_no_for_you_desc">„Önnek ajánlott” oldal megjelenítése a kezdőlapon</string>
|
||||
<string name="pref_ui_no_for_you">Önnek ajánlott oldalak</string>
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
<string name="purchase_not_found">Aplikasi tidak ditemukan</string>
|
||||
<string name="purchase_unsupported">Aplikasi tidak didukung</string>
|
||||
<string name="purchase_invalid">Aplikasi tidak dibeli</string>
|
||||
<string name="pref_ui_similar_apps_desc">Tampilkan kelompok yang serupa dan terkait pada halaman detail aplikasi</string>
|
||||
<string name="pref_ui_similar_apps">Aplikasi serupa dan terkait</string>
|
||||
<string name="pref_ui_no_for_you_desc">Tampilkan halaman Untuk Anda di layar beranda</string>
|
||||
<string name="pref_ui_no_for_you">Halaman Untuk Anda</string>
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="toast_purchase_blocked">Gli acquisti di app non sono disponibili sui profili anonimi.</string>
|
||||
<string name="insecure_anonymous_apply">Assicurati di effettuare nuovamente l\'accesso e di riavviare l\'app per applicare le modifiche.</string>
|
||||
<string name="pref_network_title">Rete</string>
|
||||
<string name="pref_ui_similar_apps_desc">Visualizza i gruppi di app simili e correlate nella pagina dei dettagli dell\'app</string>
|
||||
<string name="pref_ui_similar_apps">App simili e correlate</string>
|
||||
<string name="pref_ui_no_for_you_desc">Visualizza le pagine «Per te» sulla schermata iniziale</string>
|
||||
<string name="pref_ui_no_for_you">Pagine «Per te»</string>
|
||||
|
||||
@@ -212,7 +212,6 @@
|
||||
<string name="toast_manual_unavailable">要求したバージョン番号は利用できません。</string>
|
||||
<string name="toast_manual_available">おめでとう!要求したバージョンコードが利用できます。ダウンロード中。</string>
|
||||
<string name="title_manual_download">手動ダウンロード</string>
|
||||
<string name="pref_ui_similar_apps_desc">類似のアプリをアプリ詳細ページに表示する</string>
|
||||
<string name="pref_ui_similar_apps">類似のアプリ</string>
|
||||
<string name="pref_ui_no_for_you_desc">ホーム画面におすすめページを表示する</string>
|
||||
<string name="pref_ui_no_for_you">おすすめページ</string>
|
||||
|
||||
@@ -216,7 +216,6 @@
|
||||
<string name="toast_manual_unavailable">Koda guhertoya ku tu dixwazî tune ye.</string>
|
||||
<string name="title_manual_download">Bi destan daxe</string>
|
||||
<string name="insecure_anonymous_apply">Ji bo sepandina guhertinan zanibe be ku tu ji nû ve têketiye û sepanê ji nû ve daye destpêkirin.</string>
|
||||
<string name="pref_ui_similar_apps_desc">Komên wekhev û têkildar li ser rûpela hûrgiliyên sepanê nîşan bide</string>
|
||||
<string name="pref_ui_similar_apps">Sepanên wekhev û têkildar</string>
|
||||
<string name="pref_ui_no_for_you_desc">Rûpelên Bo Te li ser dîmendera malê rê bide</string>
|
||||
<string name="pref_ui_no_for_you">Rûpelên Bo Te</string>
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
<string name="title_apps">앱스</string>
|
||||
<string name="title_app_settings">앱 설정</string>
|
||||
<string name="purchase_failed">다운로드 실패됨</string>
|
||||
<string name="pref_ui_similar_apps_desc">앱 세부 페이지에서 유사한 및 관련 클러스터 표시</string>
|
||||
<string name="action_request_analysis">새 분석 요청</string>
|
||||
<string name="action_home_screen">홈 화면에 추가</string>
|
||||
<string name="download_clear_finished">완료건 지우기</string>
|
||||
|
||||
@@ -151,7 +151,6 @@
|
||||
<string name="download_force_clear_all">Priverstinai išvalyti visus</string>
|
||||
<string name="download_pause_all">Pristabdyti visus</string>
|
||||
<string name="pref_ui_no_for_you">„Jums“ puslapiai</string>
|
||||
<string name="pref_ui_similar_apps_desc">Rodyti programėlės puslapyje panašių ir susijusių programėlių rinkinį</string>
|
||||
<string name="tab_editor_choice">Redaktoriaus pasirinkimas</string>
|
||||
<string name="tab_for_you">Jums</string>
|
||||
<string name="title_apps_library">Programėlės bibliotekoje</string>
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
<string name="pref_ui_no_for_you">Lapas \"Tev\"</string>
|
||||
<string name="pref_ui_no_for_you_desc">Rādīt lapas \"Tev\" sākuma ekrānā</string>
|
||||
<string name="pref_ui_similar_apps">Līdzīgas un saistītas lietotnes</string>
|
||||
<string name="pref_ui_similar_apps_desc">Rādīt līdzīgas un saistītas lietotnes</string>
|
||||
<string name="purchase_failed">Lejupielāde neizdevās</string>
|
||||
<string name="tab_top_paid">Top maksas</string>
|
||||
<string name="tab_trending">Populāri</string>
|
||||
|
||||
@@ -187,7 +187,6 @@
|
||||
<string name="toast_developer_setting_failed">Skru på utviklingsinnstillingene fra enhetsinnstillingene for å åpne dem.</string>
|
||||
<string name="toast_page_unavailable">Utforskningssiden er utilgjengelig</string>
|
||||
<string name="toast_spoof_applied">Enhetslureri i bruk.</string>
|
||||
<string name="pref_ui_similar_apps_desc">Vis lignende og relaterte klynger på appdetaljsiden</string>
|
||||
<string name="pref_ui_no_for_you_desc">Vis For You-sider på startskjermen</string>
|
||||
<string name="installer_service_misconfigured">Sett opp Aurora-tjenester og innvilg alle tilganger først.</string>
|
||||
<string name="title_spoof_manager">Lureri-håndterer</string>
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
<string name="purchase_unsupported">App niet ondersteund</string>
|
||||
<string name="purchase_invalid">App niet aangeschaft</string>
|
||||
<string name="purchase_failed">Installatie mislukt</string>
|
||||
<string name="pref_ui_similar_apps_desc">Toon vergelijkbare en gerelateerde clusters op de app-detailspagina</string>
|
||||
<string name="pref_ui_similar_apps">Vergelijkbare en gerelateerde apps</string>
|
||||
<string name="pref_ui_no_for_you_desc">Pagina met aanbevelingen op thuisscherm tonen</string>
|
||||
<string name="pref_ui_no_for_you">Voor jou pagina\'s</string>
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
<string name="purchase_unsupported">ਐਪ ਸਪੋਰਟਡ ਨਹੀਂ</string>
|
||||
<string name="purchase_invalid">ਇਹ ਐਪ ਅਜੇ ਤੁਹਾਡੇ ਵਲੋਂ ਖਰੀਦਿਆ ਨਹੀਂ ਗਿਆ</string>
|
||||
<string name="purchase_failed">ਡਾਊਨਲੋਡ ਫੇਲ੍ਹ</string>
|
||||
<string name="pref_ui_similar_apps_desc">ਐਪ ਜਾਣਕਾਰੀ ਵਾਲੇ ਪੇਜ ਤੇ ਇਸੇ ਸ਼੍ਰੇਣੀ ਦੇ ਕੁੱਝ ਹੋਰ ਐਪਸ ਵਾਲਾ ਖ਼ਾਨਾ ਵਿਖਾਓ</string>
|
||||
<string name="pref_ui_similar_apps">ਇਸੇ ਸ਼੍ਰੇਣੀ ਦੇ ਕੁੱਝ ਹੋਰ ਐਪਸ</string>
|
||||
<string name="pref_ui_no_for_you_desc">ਖਾਸ ਤੁਹਾਡੇ ਲਈ ਚੁਣੇ ਹੋਏ ਐਪਸ ਵਾਲਾ ਪੰਨਾ ਵਿਖਾਓ</string>
|
||||
<string name="pref_ui_no_for_you">ਤੁਹਾਡੇ ਲਈ ਚੁਣੇ ਹੋਏ ਐਪਸ</string>
|
||||
|
||||
@@ -211,7 +211,6 @@
|
||||
<string name="pref_common_extra">Dodatkowe</string>
|
||||
<string name="pref_ui_no_for_you">Strony dla Ciebie</string>
|
||||
<string name="pref_ui_no_for_you_desc">Wyświetl strony Dla Ciebie na ekranie głównym</string>
|
||||
<string name="pref_ui_similar_apps_desc">Wyświetl grupy podobnych i powiązanych na stronie szczegółów aplikacji</string>
|
||||
<string name="pref_ui_similar_apps">Aplikacje podobne i powiązane</string>
|
||||
<string name="pref_ui_layout_tab">Wybierz domyślną kartę</string>
|
||||
<string name="pref_ui_layout">Układ</string>
|
||||
|
||||
@@ -211,7 +211,6 @@
|
||||
<string name="toast_manual_available">O código da versão solicitada está disponível, baixando agora.</string>
|
||||
<string name="toast_manual_unavailable">O código de versão que você solicitou não está disponível.</string>
|
||||
<string name="title_manual_download">Baixar manualmente</string>
|
||||
<string name="pref_ui_similar_apps_desc">Exibir apps semelhantes e relacionados na página de detalhes do app</string>
|
||||
<string name="pref_ui_no_for_you_desc">Exibir páginas de recomendação na tela inicial</string>
|
||||
<string name="pref_network_title">Rede</string>
|
||||
<string name="details_no_app_match">Não há apps correspondentes</string>
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
<string name="purchase_unsupported">Aplicação não suportada</string>
|
||||
<string name="purchase_invalid">Aplicação não comprada</string>
|
||||
<string name="purchase_failed">Descarga falhou</string>
|
||||
<string name="pref_ui_similar_apps_desc">Mostrar grupos de aplicações semelhantes e relacionadas na página de detalhes das aplicações</string>
|
||||
<string name="pref_ui_similar_apps">Aplicações semelhantes e relacionadas</string>
|
||||
<string name="pref_ui_no_for_you_desc">Exibir páginas de recomendação no ecrã inicial</string>
|
||||
<string name="pref_ui_no_for_you">Páginas de recomendação</string>
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
<string name="toast_clipboard_copied">Copiat în clipboard</string>
|
||||
<string name="pref_install_mode_session">Instalator sesiune</string>
|
||||
<string name="pref_install_mode_title">Metoda de instalare</string>
|
||||
<string name="pref_ui_similar_apps_desc">Afișare grupuri similare și asociate pe pagina de detalii a aplicației</string>
|
||||
<string name="pref_network_title">Rețea</string>
|
||||
<string name="session_good">Vai! Toate bune.</string>
|
||||
<string name="title_purchase_history">Istoricul achizițiilor</string>
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="toast_purchase_blocked">Покупка приложений недоступна когда анонимно.</string>
|
||||
<string name="insecure_anonymous_apply">Убедитесь, что выполнили новый вход и перезапустили приложение для применения изменений.</string>
|
||||
<string name="pref_network_title">Сеть</string>
|
||||
<string name="pref_ui_similar_apps_desc">Показать секции похожих и связанных на странице приложения</string>
|
||||
<string name="pref_ui_no_for_you_desc">Показать страницу рекомендаций на главном экране</string>
|
||||
<string name="pref_ui_layout_tab">Вкладка по умолчанию</string>
|
||||
<string name="pref_ui_layout">Вкладки</string>
|
||||
|
||||
@@ -215,7 +215,6 @@
|
||||
<string name="about_bhim">BHIM - UPI</string>
|
||||
<string name="about_bhim_summary">Dona pro mèdiu de UPI</string>
|
||||
<string name="download_metadata">Retzende meta-datos</string>
|
||||
<string name="pref_ui_similar_apps_desc">Ammustra sos grupos de aplicatziones simigiantes e ligados in sa pàgina de sos detàllios de s\'aplicatzione</string>
|
||||
<string name="about_fdroid_summary">Otene Aurora Store pro mèdiu de F-Droid.</string>
|
||||
<string name="pref_install_mode_services">Servìtzios de Aurora</string>
|
||||
<string name="title_installation">Installatzione</string>
|
||||
|
||||
@@ -234,7 +234,6 @@
|
||||
<string name="download_eta_min">වි. %1$d ත. %2$d ක් ඉතිරිය</string>
|
||||
<string name="download_eta_sec">ත. %1$d ක් ඉතිරිය</string>
|
||||
<string name="onboarding_permission_notifications">දැනුම්දීම්</string>
|
||||
<string name="pref_ui_similar_apps_desc">යෙදුමේ විස්තර පිටුවේ සමාන හා ආශ්රිත පර්ෂද පෙන්වන්න</string>
|
||||
<string name="session_good">ඔක්කොම හරි!</string>
|
||||
<string name="session_enjoy">ඇතුළු වී අත්විඳින්න.</string>
|
||||
<string name="session_init">සූදානම් කෙරෙමින්…</string>
|
||||
|
||||
@@ -140,7 +140,6 @@
|
||||
<string name="pref_install_mode_shizuku">Shizuku inštalátor</string>
|
||||
<string name="pref_install_mode_session">Inštalátor relácie</string>
|
||||
<string name="pref_vending_version_device">Predvolené (z konfigurácie zariadenia)</string>
|
||||
<string name="pref_ui_similar_apps_desc">Zobraziť podobné a súvisiace skupiny na stránke podrobností aplikácie</string>
|
||||
<string name="pref_updates_incompatible">Nekompatibilné aktualizácie</string>
|
||||
<string name="pref_updates_incompatible_desc">Zobraziť aktualizácie pre nekompatibilné alebo zakázané aplikácie, ktoré môžu zlyhať pri nštalovaní</string>
|
||||
<string name="pref_aurora_only">Iba aplikácie Aurora obchod</string>
|
||||
|
||||
@@ -272,7 +272,6 @@
|
||||
<string name="pref_filter_fdroid_title">Filtrirajte F-Droid aplikacije</string>
|
||||
<string name="pref_ui_title">Prilagajanje</string>
|
||||
<string name="pref_ui_no_for_you">Strani za vas</string>
|
||||
<string name="pref_ui_similar_apps_desc">Prikaži podobne in sorodne gruče na strani s podrobnostmi o aplikaciji</string>
|
||||
<string name="menu">Meni</string>
|
||||
<string name="about_wiki_title">Wiki</string>
|
||||
<string name="about_wiki_summary">Poiščite odgovore na pogosto zastavljena vprašanja (F.A.Q.), korake za odpravljanje težav in več</string>
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
<string name="purchase_unsupported">Appka lama taageerin</string>
|
||||
<string name="purchase_invalid">Appka lama iibsanin</string>
|
||||
<string name="purchase_failed">Dajintii maguulaysan</string>
|
||||
<string name="pref_ui_similar_apps_desc">Soodhig appska lamidka ah iyo kuwa laxidhiidha bogga faahfaahinta appka</string>
|
||||
<string name="pref_ui_similar_apps">Lamid ah & laxidhiidha</string>
|
||||
<string name="pref_ui_no_for_you_desc">Boggaga Adiga shaashada hore soodhig</string>
|
||||
<string name="pref_ui_no_for_you">Boggaga Adiga</string>
|
||||
|
||||
@@ -173,7 +173,6 @@
|
||||
<string name="pref_ui_no_for_you">Faqe “Për ju”</string>
|
||||
<string name="pref_ui_no_for_you_desc">Shfaq te skena e kreut faqe “Për ju”</string>
|
||||
<string name="pref_ui_similar_apps">Aplikacione të ngjashme dhe të afërta</string>
|
||||
<string name="pref_ui_similar_apps_desc">Shfaq te faqja e hollësive të aplikacionit grumbuj të ngjashëm dhe të afërt</string>
|
||||
<string name="purchase_failed">Shkarkimi dështoi</string>
|
||||
<string name="purchase_invalid">Aplikacion jo i blerë</string>
|
||||
<string name="purchase_unsupported">Aplikacioni nuk mbulohet</string>
|
||||
|
||||
@@ -215,7 +215,6 @@
|
||||
<string name="toast_manual_unavailable">Кôд верзије који тражите није доступан.</string>
|
||||
<string name="title_manual_download">Ручно преузимање</string>
|
||||
<string name="insecure_anonymous_apply">Обавезно се поново пријавите и поново покрените апликацију да бисте применили промене.</string>
|
||||
<string name="pref_ui_similar_apps_desc">Прикажите сличне и сродне скупове на страници с детаљима о апликацији</string>
|
||||
<string name="pref_ui_similar_apps">Сличне и сродне апликације</string>
|
||||
<string name="pref_ui_no_for_you_desc">Прикажите странице за вас на почетном екрану</string>
|
||||
<string name="pref_ui_no_for_you">Странице за вас</string>
|
||||
|
||||
@@ -202,7 +202,6 @@
|
||||
<string name="toast_export_failed">Kunde inte exportera enhetens konfiguration</string>
|
||||
<string name="toast_export_success">Enhetens konfiguration exporterades</string>
|
||||
<string name="toast_apk_blacklisted">Svartlistad</string>
|
||||
<string name="pref_ui_similar_apps_desc">Visa liknande och relaterade klustrar på appens detaljsida</string>
|
||||
<string name="onboarding_permission_esm">Hanterare av externt lagringsutrymme</string>
|
||||
<string name="details_no_permission">Inga behörigheter</string>
|
||||
<string name="action_pending">Avvaktande</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user