compose: accounts: Initial migration

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-09-17 17:18:12 +05:30
parent ea9827c6df
commit b993700f8f
15 changed files with 296 additions and 426 deletions

View File

@@ -24,7 +24,7 @@ object Constants {
const val PARCEL_DOWNLOAD = "PARCEL_DOWNLOAD"
const val URL_TOS = "https://play.google.com/about/play-terms/"
const val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/LICENSE"
const val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/-/tree/master/LICENSES"
const val URL_DISCLAIMER = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/DISCLAIMER.md"
const val URL_POLICY = "https://gitlab.com/AuroraOSS/AuroraStore/-/blob/master/POLICY.md"

View File

@@ -5,9 +5,12 @@
package com.aurora.store.compose.navigation
import android.content.Intent
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation.NavDeepLinkBuilder
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entry
import androidx.navigation3.runtime.entryProvider
@@ -15,6 +18,9 @@ import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.compose.ui.accounts.AccountsScreen
import com.aurora.store.compose.ui.blacklist.BlacklistScreen
import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.compose.ui.dev.DevProfileScreen
@@ -86,6 +92,23 @@ fun NavDisplay(startDestination: NavKey) {
}
)
}
entry<Screen.Accounts> {
// TODO: Rework when migrating splash fragment to compose
val splashIntent = NavDeepLinkBuilder(LocalContext.current)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.splashFragment)
.setComponentName(MainActivity::class.java)
.createTaskStackBuilder()
.intents
.first()
.apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) }
AccountsScreen(
onNavigateUp = { onNavigateUp() },
onNavigateToSplash = { activity?.startActivity(splashIntent) }
)
}
}
)
}

View File

@@ -41,4 +41,7 @@ sealed class Screen : NavKey, Parcelable {
@Serializable
data object Downloads : Screen()
@Serializable
data object Accounts : Screen()
}

View File

@@ -0,0 +1,210 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.accounts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.AssistChip
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.aurora.Constants.URL_DISCLAIMER
import com.aurora.Constants.URL_LICENSE
import com.aurora.Constants.URL_TOS
import com.aurora.extensions.browse
import com.aurora.store.R
import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.viewmodel.accounts.AccountsViewModel
@Composable
fun AccountsScreen(
onNavigateUp: () -> Unit,
onNavigateToSplash: () -> Unit,
viewModel: AccountsViewModel = hiltViewModel()
) {
val userProfile = viewModel.authProvider.authData?.userProfile
val avatar = when {
viewModel.authProvider.isAnonymous -> R.mipmap.ic_launcher
userProfile != null -> userProfile.artwork.url
else -> R.mipmap.ic_launcher
}
val name = when {
viewModel.authProvider.isAnonymous -> stringResource(R.string.account_anonymous)
userProfile != null -> userProfile.name
else -> stringResource(R.string.account_anonymous)
}
val email = when {
viewModel.authProvider.isAnonymous -> stringResource(R.string.account_anonymous_email)
userProfile != null -> userProfile.email
else -> stringResource(R.string.account_anonymous_email)
}
val context = LocalContext.current
var shouldShowLogoutDialog by rememberSaveable { mutableStateOf(false) }
if (shouldShowLogoutDialog) {
LogoutDialog(
onConfirm = {
shouldShowLogoutDialog = false
AccountProvider.logout(context)
onNavigateToSplash()
},
onDismiss = { shouldShowLogoutDialog = false }
)
}
ScreenContent(
onNavigateUp = onNavigateUp,
avatar = avatar,
name = name,
email = email,
onLogout = { shouldShowLogoutDialog = true }
)
}
@Composable
private fun ScreenContent(
avatar: Any = R.mipmap.ic_launcher,
name: String = stringResource(R.string.account_anonymous),
email: String = stringResource(R.string.account_anonymous_email),
onNavigateUp: () -> Unit = {},
onLogout: () -> Unit = {}
) {
Scaffold(
topBar = {
TopAppBarComposable(
title = stringResource(R.string.title_account_manager),
onNavigateUp = onNavigateUp
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(vertical = dimensionResource(R.dimen.padding_medium)),
verticalArrangement = Arrangement.SpaceBetween
) {
AssistHeader()
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.crossfade(true)
.data(avatar)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.requiredSize(dimensionResource(R.dimen.icon_size_avatar))
.clip(CircleShape)
)
Text(
text = name,
style = MaterialTheme.typography.titleLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = email,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.height_bottom_adj)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
) {
Text(
text = stringResource(R.string.account_logout),
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedButton(onClick = onLogout) {
Icon(
painter = painterResource(R.drawable.ic_logout),
contentDescription = null
)
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.padding_xsmall)))
Text(
text = stringResource(R.string.action_logout)
)
}
}
}
}
}
@Composable
private fun AssistHeader() {
val context = LocalContext.current
val links = mapOf(
R.string.menu_terms to URL_TOS,
R.string.menu_disclaimer to URL_DISCLAIMER,
R.string.menu_license to URL_LICENSE
)
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.padding_medium)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal))
) {
items(items = links.keys.toList(), key = { item -> item }) { label ->
AssistChip(
onClick = { context.browse(links.getValue(label)) },
label = { Text(text = stringResource(label)) }
)
}
}
}
@Preview
@Composable
private fun AccountsScreenPreview() {
ScreenContent()
}

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.accounts
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* Dialog for confirming log out
* @param onConfirm Callback when the token has been entered
* @param onDismiss Callback when the dialog has been dismissed
*/
@Composable
fun LogoutDialog(onConfirm: () -> Unit = {}, onDismiss: () -> Unit = {}) {
AlertDialog(
title = { Text(text = stringResource(R.string.action_logout_confirmation_title)) },
text = {
Text(text = stringResource(R.string.action_logout_confirmation_message))
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = onConfirm) {
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = stringResource(android.R.string.cancel))
}
}
)
}
@Preview
@Composable
private fun LogoutDialogPreview() {
LogoutDialog()
}

View File

@@ -8,6 +8,7 @@ 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.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -156,6 +157,7 @@ private fun FilterHeader(onClick: (filter: Review.Filter) -> Unit) {
)
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal))
) {
items(items = filters.keys.toList(), key = { item -> item }) { filter ->

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -376,7 +377,9 @@ private fun FilterHeader(
}
LazyRow(
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium)),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.padding_medium)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal))
) {
items(items = filters, key = { item -> item }) { filter ->

View File

@@ -1,74 +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.account
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import coil3.load
import coil3.request.placeholder
import coil3.request.transformations
import coil3.transform.RoundedCornersTransformation
import com.aurora.Constants.URL_DISCLAIMER
import com.aurora.Constants.URL_LICENSE
import com.aurora.Constants.URL_TOS
import com.aurora.extensions.browse
import com.aurora.store.R
import com.aurora.store.databinding.FragmentAccountBinding
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.account.AccountViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AccountFragment : BaseFragment<FragmentAccountBinding>() {
private val viewModel: AccountViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Toolbar
binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
// Chips
view.context.apply {
binding.chipDisclaimer.setOnClickListener { browse(URL_DISCLAIMER) }
binding.chipLicense.setOnClickListener { browse(URL_LICENSE) }
binding.chipTos.setOnClickListener { browse(URL_TOS) }
}
viewModel.authProvider.authData?.userProfile?.let {
val avatar =
if (viewModel.authProvider.isAnonymous) R.mipmap.ic_launcher else it.artwork.url
binding.imgAvatar.load(avatar) {
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(32F))
}
binding.txtName.text = if (viewModel.authProvider.isAnonymous) "Anonymous" else it.name
binding.txtEmail.text =
if (viewModel.authProvider.isAnonymous) "anonymous@gmail.com" else it.email
}
binding.btnLogout.addOnClickListener {
findNavController().navigate(R.id.logoutDialog)
}
}
}

View File

@@ -1,28 +0,0 @@
package com.aurora.store.view.ui.account
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.findNavController
import com.aurora.store.R
import com.aurora.store.data.providers.AccountProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class LogoutDialog: DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.action_logout_confirmation_title)
.setMessage(R.string.action_logout_confirmation_message)
.setPositiveButton(getString(android.R.string.ok)) { _, _ -> logout() }
.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dialog?.dismiss()}
.create()
}
private fun logout() {
AccountProvider.logout(requireContext())
findNavController().navigate(LogoutDialogDirections.actionLogoutDialogToSplashFragment())
}
}

View File

@@ -318,7 +318,7 @@ class MoreDialogFragment : DialogFragment() {
}
}
OutlinedButton(
onClick = { findNavController().navigate(R.id.accountFragment) },
onClick = { requireContext().navigate(Screen.Accounts) },
shape = RoundedCornerShape(12.dp),
border = BorderStroke(
1.dp,

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.viewmodel.account
package com.aurora.store.viewmodel.accounts
import androidx.lifecycle.ViewModel
import com.aurora.store.data.providers.AuthProvider
@@ -11,4 +11,4 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class AccountViewModel @Inject constructor(val authProvider: AuthProvider) : ViewModel()
class AccountsViewModel @Inject constructor(val authProvider: AuthProvider) : ViewModel()