diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ae69faf2b..d67a10fea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -157,6 +157,7 @@ dependencies { //Coil implementation(libs.coil.kt) + implementation(libs.coil.compose) //Shimmer implementation(libs.facebook.shimmer) diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt index 51f58be73..c23296abb 100644 --- a/app/src/main/java/com/aurora/Constants.kt +++ b/app/src/main/java/com/aurora/Constants.kt @@ -25,6 +25,11 @@ object Constants { const val APP_ID = "com.aurora.store" + 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_DISCLAIMER = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/DISCLAIMER.md" + const val URL_POLICY = "https://gitlab.com/AuroraOSS/AuroraStore/-/blob/master/POLICY.md" + const val EXODUS_SUBMIT_PAGE = "https://reports.exodus-privacy.eu.org/analysis/submit/#" const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/" const val SHARE_URL = "https://play.google.com/store/apps/details?id=" diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt index 7d9b4cedc..f656f48a4 100644 --- a/app/src/main/java/com/aurora/store/MainActivity.kt +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -20,32 +20,17 @@ package com.aurora.store -import android.content.Intent import android.content.res.ColorStateList -import android.os.Build import android.os.Bundle -import android.os.Environment -import android.provider.Settings -import android.view.Menu -import android.view.MenuItem import android.view.View -import android.widget.ImageView -import android.widget.TextView import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import androidx.core.graphics.ColorUtils -import androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED -import androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED import androidx.lifecycle.lifecycleScope import androidx.navigation.FloatingWindow import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController -import coil.load -import coil.transform.RoundedCornersTransformation import com.aurora.Constants import com.aurora.extensions.accentColor import com.aurora.extensions.applyThemeAccent @@ -56,7 +41,6 @@ import com.aurora.store.util.Log import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB import com.aurora.store.view.ui.sheets.NetworkDialogSheet -import com.google.android.material.bottomnavigation.BottomNavigationView import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -65,7 +49,6 @@ class MainActivity : AppCompatActivity() { private lateinit var B: ActivityMainBinding private lateinit var navController: NavController - private lateinit var appConfig: AppBarConfiguration // TopLevelFragments private val topLevelFrags = listOf( @@ -111,11 +94,11 @@ class MainActivity : AppCompatActivity() { } } - // Toolbar - setSupportActionBar(B.toolbar) - - attachNavigation() - attachDrawer() + B.navView.apply { + val alphaColor = ColorUtils.setAlphaComponent(this@MainActivity.accentColor(), 100) + setupWithNavController(navController) + itemActiveIndicatorColor = ColorStateList.valueOf(alphaColor) + } // Handle quick exit from back actions val defaultTab = when (Preferences.getInteger(this, PREFERENCE_DEFAULT_SELECTED_TAB)) { @@ -124,21 +107,17 @@ class MainActivity : AppCompatActivity() { else -> R.id.appsContainerFragment } onBackPressedDispatcher.addCallback(this) { - if (!B.drawerLayout.isOpen) { - if (navController.currentDestination?.id in topLevelFrags) { - if (navController.currentDestination?.id == defaultTab) { - finish() - } else { - navController.navigate(defaultTab) - } - } else if (navHostFragment.childFragmentManager.backStackEntryCount == 0) { - // We are on either on onboarding or splash fragment + if (navController.currentDestination?.id in topLevelFrags) { + if (navController.currentDestination?.id == defaultTab) { finish() } else { - navController.navigateUp() + navController.navigate(defaultTab) } + } else if (navHostFragment.childFragmentManager.backStackEntryCount == 0) { + // We are on either on onboarding or splash fragment + finish() } else { - B.drawerLayout.close() + navController.navigateUp() } } @@ -152,84 +131,13 @@ class MainActivity : AppCompatActivity() { navController.addOnDestinationChangedListener { _, navDestination, _ -> if (navDestination !is FloatingWindow) { when (navDestination.id) { - in topLevelFrags -> { - B.navView.visibility = View.VISIBLE - B.toolbar.visibility = View.VISIBLE - B.drawerLayout.setDrawerLockMode(LOCK_MODE_UNLOCKED) - } - - else -> { - hideTopLevelOnlyViews() - } + in topLevelFrags -> B.navView.visibility = View.VISIBLE + else -> B.navView.visibility = View.GONE } } } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_download_manager -> { - navController.navigate(R.id.downloadFragment) - return true - } - } - return super.onOptionsItemSelected(item) - } - - private fun hideTopLevelOnlyViews() { - B.navView.visibility = View.GONE - B.toolbar.visibility = View.GONE - B.drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED) - } - - private fun attachNavigation() { - val bottomNavigationView: BottomNavigationView = B.navView - bottomNavigationView.setupWithNavController(navController) - - bottomNavigationView.apply { - val alphaColor = ColorUtils.setAlphaComponent(this@MainActivity.accentColor(), 100) - itemActiveIndicatorColor = ColorStateList.valueOf(alphaColor) - } - } - - private fun attachDrawer() { - val headerView: View = B.navigation.getHeaderView(0) - - headerView.let { - it.findViewById(R.id.img)?.load(R.mipmap.ic_launcher) { - transformations(RoundedCornersTransformation(8F)) - } - it.findViewById(R.id.txt_name)?.text = getString(R.string.app_name) - it.findViewById(R.id.txt_email)?.text = getString( - R.string.version, - BuildConfig.VERSION_NAME, - BuildConfig.VERSION_CODE - ) - } - - appConfig = AppBarConfiguration.Builder(topLevelFrags.toSet()) - .setOpenableLayout(B.root) - .build() - setupActionBarWithNavController(navController, appConfig) - B.navigation.setupWithNavController(navController) - } - - override fun onSupportNavigateUp(): Boolean { - return navController.navigateUp(appConfig) - } - - private fun checkExternalStorageManagerPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (!Environment.isExternalStorageManager()) - startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) - } - } - private fun isIntroDone(): Boolean { return Preferences.getBoolean(this@MainActivity, Preferences.PREFERENCE_INTRO) } 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 index 4761350db..adc03b46c 100644 --- 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 @@ -26,6 +26,9 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import coil.load import coil.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.gplayapi.data.models.AuthData import com.aurora.store.R @@ -48,11 +51,6 @@ class AccountFragment : Fragment(R.layout.fragment_account) { private lateinit var authData: AuthData private lateinit var accountProvider: AccountProvider - private val URL_TOS = "https://play.google.com/about/play-terms/" - private val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/LICENSE" - private val URL_DISCLAIMER = - "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/DISCLAIMER.md" - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentAccountBinding.bind(view) diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt index 55fdc4987..85cff4057 100644 --- a/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt @@ -26,6 +26,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.navigation.fragment.findNavController import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.store.MobileNavigationDirections import com.aurora.store.R import com.aurora.store.data.providers.AuthProvider import com.aurora.store.databinding.FragmentAppsGamesBinding @@ -48,6 +49,24 @@ class AppsContainerFragment : Fragment(R.layout.fragment_apps_games) { super.onViewCreated(view, savedInstanceState) _binding = FragmentAppsGamesBinding.bind(view) + // Toolbar + binding.toolbar.apply { + title = getString(R.string.title_apps) + setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_download_manager -> { + findNavController().navigate(R.id.downloadFragment) + } + R.id.menu_more -> { + findNavController().navigate( + MobileNavigationDirections.actionGlobalMoreDialogFragment() + ) + } + } + true + } + } + // ViewPager val isForYouEnabled = Preferences.getBoolean( requireContext(), 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 new file mode 100644 index 000000000..b85f13aad --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt @@ -0,0 +1,282 @@ +package com.aurora.store.view.ui.commons + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEach +import androidx.core.graphics.ColorUtils +import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.findNavController +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.aurora.Constants +import com.aurora.Constants.URL_TOS +import com.aurora.extensions.accentColor +import com.aurora.extensions.browse +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class MoreDialogFragment : DialogFragment() { + + private data class Option( + @StringRes val title: Int, + @DrawableRes val icon: Int, + @IdRes val destinationID: Int + ) + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext()) + .setView(customDialogView(requireContext())) + .create() + } + + private fun customDialogView(context: Context): ComposeView { + return ComposeView(context).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + val bColor = ColorUtils.setAlphaComponent(LocalContext.current.accentColor(), 20) + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = Color(bColor)) + .verticalScroll(rememberScrollState()) + .padding(13.dp), + verticalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterVertically) + ) { + AppBar() + AccountHeader() + Column( + modifier = Modifier + .clip( + RoundedCornerShape( + topStart = 5.dp, + topEnd = 5.dp, + bottomStart = 20.dp, + bottomEnd = 20.dp + ) + ) + .background(color = Color.White) + ) { + getOptions().fastForEach { option -> OptionItem(option = option) } + } + getExtraOptions().fastForEach { option -> OptionItem(option = option) } + Footer() + } + } + } + } + + @Composable + fun AppBar() { + Box(contentAlignment = Alignment.CenterStart) { + IconButton(onClick = { findNavController().navigateUp() }) { + Icon( + painter = painterResource(id = R.drawable.ic_cancel), + contentDescription = stringResource(id = R.string.action_cancel), + tint = Color.Black + ) + } + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.app_name), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center + ) + } + } + + @Composable + fun Footer() { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally) + ) { + TextButton(onClick = { requireContext().browse(Constants.URL_POLICY) }) { + Text( + text = stringResource(id = R.string.privacy_policy_title), + color = Color.Black + ) + } + Text(text = "•") + TextButton(onClick = { requireContext().browse(URL_TOS) }) { + Text( + text = stringResource(id = R.string.menu_terms), + color = Color.Black + ) + } + } + } + + @Composable + private fun AccountHeader() { + val authData = AuthProvider.with(LocalContext.current).getAuthData() + Column( + modifier = Modifier + .fillMaxWidth() + .clip( + RoundedCornerShape( + topStart = 20.dp, + topEnd = 20.dp, + bottomStart = 5.dp, + bottomEnd = 5.dp + ) + ) + .background(color = Color.White) + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(15.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(15.dp, Alignment.Start) + ) { + SubcomposeAsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(if (authData.isAnonymous) R.mipmap.ic_launcher else authData.userProfile?.artwork?.url) + .placeholder(R.drawable.ic_account) + .crossfade(true) + .build(), + contentDescription = stringResource(id = R.string.title_account_manager), + contentScale = ContentScale.Crop, + modifier = Modifier + .requiredSize(48.dp) + .clip(CircleShape) + ) + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start + ) { + Text( + text = if (authData.isAnonymous) "anonymous" else authData.userProfile!!.name, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp + ) + Text( + text = if (authData.isAnonymous) "anonymous@gmail.com" else authData.userProfile!!.email, + fontWeight = FontWeight.Normal, + fontSize = 14.sp + ) + } + } + OutlinedButton( + onClick = { findNavController().navigate(R.id.accountFragment) }, + shape = RoundedCornerShape(12.dp) + ) { + Text( + text = stringResource(id = R.string.manage_account), + color = Color.Black + ) + } + } + } + + @Composable + private fun OptionItem(option: Option) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { findNavController().navigate(option.destinationID) } + .padding(15.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start) + ) { + Image( + painter = painterResource(id = option.icon), + contentDescription = stringResource(id = option.title), + colorFilter = ColorFilter.tint(color = Color.Black), + modifier = Modifier + .padding(horizontal = 10.dp) + .requiredSize(25.dp) + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = option.title), + color = Color.Black, + fontSize = 16.sp, + ) + } + } + + private fun getOptions(): List