diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt index 536f25e11..c5ab466cd 100644 --- a/app/src/main/java/com/aurora/Constants.kt +++ b/app/src/main/java/com/aurora/Constants.kt @@ -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" diff --git a/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt b/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt index e6a2e0ad3..5e6976a7f 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt @@ -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 { + // 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) } + ) + } } ) } diff --git a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt index 9156b4779..e927638c7 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt @@ -41,4 +41,7 @@ sealed class Screen : NavKey, Parcelable { @Serializable data object Downloads : Screen() + + @Serializable + data object Accounts : Screen() } diff --git a/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt new file mode 100644 index 000000000..9d054d12d --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt @@ -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() +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/accounts/LogoutDialog.kt b/app/src/main/java/com/aurora/store/compose/ui/accounts/LogoutDialog.kt new file mode 100644 index 000000000..1ad8c603a --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/accounts/LogoutDialog.kt @@ -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() +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt index b78050706..7beb24bf1 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt @@ -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 -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt index 9807121eb..f45c72a91 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt @@ -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 -> diff --git a/app/src/main/java/com/aurora/store/view/ui/account/AccountFragment.kt b/app/src/main/java/com/aurora/store/view/ui/account/AccountFragment.kt deleted file mode 100644 index f3f48979f..000000000 --- a/app/src/main/java/com/aurora/store/view/ui/account/AccountFragment.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * 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 . - * - */ - -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() { - - 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) - } - } -} diff --git a/app/src/main/java/com/aurora/store/view/ui/account/LogoutDialog.kt b/app/src/main/java/com/aurora/store/view/ui/account/LogoutDialog.kt deleted file mode 100644 index 49c536d84..000000000 --- a/app/src/main/java/com/aurora/store/view/ui/account/LogoutDialog.kt +++ /dev/null @@ -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()) - } -} diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt index abf508b71..90103d966 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt @@ -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, diff --git a/app/src/main/java/com/aurora/store/viewmodel/account/AccountViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/accounts/AccountsViewModel.kt similarity index 68% rename from app/src/main/java/com/aurora/store/viewmodel/account/AccountViewModel.kt rename to app/src/main/java/com/aurora/store/viewmodel/accounts/AccountsViewModel.kt index 582340ba5..aca34c151 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/account/AccountViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/accounts/AccountsViewModel.kt @@ -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() diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml index 4066409bc..4b96d77dd 100644 --- a/app/src/main/res/drawable/ic_logout.xml +++ b/app/src/main/res/drawable/ic_logout.xml @@ -1,28 +1,14 @@ + - diff --git a/app/src/main/res/layout-land/fragment_account.xml b/app/src/main/res/layout-land/fragment_account.xml deleted file mode 100644 index e528df593..000000000 --- a/app/src/main/res/layout-land/fragment_account.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml deleted file mode 100644 index ec65b74b1..000000000 --- a/app/src/main/res/layout/fragment_account.xml +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 8bdea5fca..e9a47dc2e 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -76,11 +76,6 @@ android:name="com.aurora.store.view.ui.commons.FavouriteFragment" android:label="@string/title_favourites_manager" tools:layout="@layout/fragment_generic_with_toolbar" /> - - - -