legacy: remove fragment-based screens and sheets

Drops every Fragment, DialogFragment and BottomSheet in view/ui plus
the flavoured SplashFragment variants. All call sites now route
through compose/ui equivalents and ComposeActivity.
This commit is contained in:
Rahul Patel
2026-05-19 12:14:03 +05:30
parent fef75cb5b3
commit 6dc1cf2a8c
30 changed files with 0 additions and 3414 deletions

View File

@@ -1,134 +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.Build
import android.os.Bundle
import android.view.View
import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.AuroraApp
import com.aurora.store.R
import com.aurora.store.data.event.AuthEvent
import com.aurora.store.databinding.FragmentGoogleBinding
import com.aurora.store.util.AC2DMUtil
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.auth.AuthViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class GoogleFragment : BaseFragment<FragmentGoogleBinding>() {
private val viewModel: AuthViewModel by activityViewModels()
companion object {
const val EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup"
const val AUTH_TOKEN = "oauth_token"
private const val JS_SCRIPT =
"(function() { return document.getElementById('profileIdentifier').innerHTML; })();"
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val cookieManager = CookieManager.getInstance()
binding.webview.apply {
cookieManager.removeAllCookies(null)
cookieManager.acceptThirdPartyCookies(this)
cookieManager.setAcceptThirdPartyCookies(this, true)
webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
if (newProgress != 0) {
binding.progressBar.also {
it.isVisible = newProgress < 100
it.isIndeterminate = false
it.max = 100
it.progress = newProgress
}
} else {
binding.progressBar.isIndeterminate = true
}
}
}
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
val cookies = CookieManager.getInstance().getCookie(url)
// cookies can be null if there is an error
if (cookies != null) {
val cookieMap = AC2DMUtil.parseCookieString(cookies)
if (cookieMap.isNotEmpty() && cookieMap[AUTH_TOKEN] != null) {
val oauthToken = cookieMap[AUTH_TOKEN]
evaluateJavascript(JS_SCRIPT) {
val email = it.replace("\"".toRegex(), "")
viewModel.buildAuthData(view.context, email, oauthToken)
}
}
}
}
}
settings.apply {
allowContentAccess = true
domStorageEnabled = true
javaScriptEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) safeBrowsingEnabled = false
}
loadUrl(EMBEDDED_SETUP_URL)
}
viewLifecycleOwner.lifecycleScope.launch {
AuroraApp.events.authEvent.collect { event ->
if (event is AuthEvent.GoogleLogin) onEventReceived(event)
}
}
}
private fun onEventReceived(event: AuthEvent.GoogleLogin) {
if (event.success) {
viewModel.buildGoogleAuthData(event.email, event.token, AuthHelper.Token.AAS)
} else {
Toast.makeText(
requireContext(),
getString(R.string.toast_aas_token_failed),
Toast.LENGTH_LONG
).show()
}
findNavController().navigate(
GoogleFragmentDirections.actionGoogleFragmentToSplashFragment()
)
}
}

View File

@@ -1,148 +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.apps
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.aurora.extensions.navigate
import com.aurora.store.MobileNavigationDirections
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.databinding.FragmentAppsGamesBinding
import com.aurora.store.util.Preferences
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.view.ui.commons.CategoryFragment
import com.aurora.store.view.ui.commons.ForYouFragment
import com.aurora.store.view.ui.commons.TopChartContainerFragment
import com.aurora.store.viewmodel.apps.AppsContainerViewModel
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AppsContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
private val viewModel: AppsContainerViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Adjust FAB margins for edgeToEdge display
ViewCompat.setOnApplyWindowInsetsListener(binding.searchFab) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
binding.searchFab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.bottom + resources.getDimensionPixelSize(R.dimen.margin_large)
}
WindowInsetsCompat.CONSUMED
}
// Toolbar
binding.toolbar.apply {
title = getString(R.string.title_apps)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_download_manager -> {
requireContext().navigate(Screen.Downloads)
}
R.id.menu_more -> {
findNavController().navigate(
MobileNavigationDirections.actionGlobalMoreDialogFragment()
)
}
}
true
}
}
// ViewPager
val isForYouEnabled = Preferences.getBoolean(
requireContext(),
Preferences.PREFERENCE_FOR_YOU
)
binding.pager.adapter = ViewPagerAdapter(
childFragmentManager,
viewLifecycleOwner.lifecycle,
!viewModel.authProvider.isAnonymous,
isForYouEnabled
)
binding.pager.isUserInputEnabled =
false // Disable viewpager scroll to avoid scroll conflicts
val tabTitles: MutableList<String> = mutableListOf<String>().apply {
if (isForYouEnabled) {
add(getString(R.string.tab_for_you))
}
add(getString(R.string.tab_top_charts))
add(getString(R.string.tab_categories))
}
TabLayoutMediator(
binding.tabLayout,
binding.pager,
true
) { tab: TabLayout.Tab, position: Int ->
tab.text = tabTitles[position]
}.attach()
binding.searchFab.setOnClickListener {
requireContext().navigate(Screen.Search)
}
}
override fun onDestroyView() {
binding.pager.adapter = null
super.onDestroyView()
}
internal class ViewPagerAdapter(
fragment: FragmentManager,
lifecycle: Lifecycle,
private val isGoogleAccount: Boolean,
private val isForYouEnabled: Boolean
) :
FragmentStateAdapter(fragment, lifecycle) {
private val tabFragments: MutableList<Fragment> = mutableListOf<Fragment>().apply {
if (isForYouEnabled) {
add(ForYouFragment.newInstance(0))
}
add(TopChartContainerFragment.newInstance(0))
add(CategoryFragment.newInstance(0))
}
override fun createFragment(position: Int): Fragment = tabFragments[position]
override fun getItemCount(): Int = tabFragments.size
}
}

View File

@@ -1,153 +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.commons
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import com.airbnb.epoxy.EpoxyRecyclerView
import com.aurora.extensions.TAG
import com.aurora.extensions.navigate
import com.aurora.gplayapi.data.models.Category
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.store.MobileNavigationDirections
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.data.model.MinimalApp
import com.aurora.store.data.providers.PermissionProvider
import java.lang.reflect.ParameterizedType
abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
lateinit var permissionProvider: PermissionProvider
protected open var viewBindingType: ViewBindingType? = null
protected val binding get() = viewBindingType!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionProvider = PermissionProvider(this)
}
@Suppress("UNCHECKED_CAST")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val type = (javaClass.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<ViewBindingType>
val method = type.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
)
viewBindingType = method.invoke(null, inflater, container, false) as ViewBindingType
return binding.root
}
override fun onDestroy() {
permissionProvider.unregister()
super.onDestroy()
}
override fun onDestroyView() {
cleanupRecyclerViews(findAllRecyclerViews(requireView()))
viewBindingType = null
super.onDestroyView()
}
fun openDetailsFragment(packageName: String) {
requireContext().navigate(
Screen.AppDetails(packageName)
)
}
fun openCategoryBrowseFragment(category: Category) {
findNavController().navigate(
MobileNavigationDirections.actionGlobalCategoryBrowseFragment(
category.title,
category.browseUrl
)
)
}
fun openStreamBrowseFragment(browseUrl: String, title: String = "") {
if (browseUrl.lowercase().contains("expanded")) {
findNavController().navigate(
MobileNavigationDirections.actionGlobalExpandedStreamBrowseFragment(
title,
browseUrl
)
)
} else if (browseUrl.lowercase().contains("developer")) {
findNavController().navigate(
MobileNavigationDirections.actionGlobalDevProfileFragment(
browseUrl.substringAfter("developer-"),
title
)
)
}
}
fun openStreamBrowseFragment(streamCluster: StreamCluster) {
requireContext().navigate(Screen.StreamBrowse(streamCluster))
}
fun openAppMenuSheet(app: MinimalApp) {
findNavController().navigate(MobileNavigationDirections.actionGlobalAppMenuSheet(app))
}
fun openGMSWarningFragment() {
// TODO: FIX ME
}
private fun cleanupRecyclerViews(recyclerViews: List<EpoxyRecyclerView>) {
recyclerViews.forEach { recyclerView ->
runCatching {
recyclerView.adapter?.let {
recyclerView.swapAdapter(it, true)
}
}.onFailure {
Log.e(TAG, "Failed to cleanup RecyclerView", it)
}
}
}
private fun findAllRecyclerViews(view: View): List<EpoxyRecyclerView> {
val recyclerViews = mutableListOf<EpoxyRecyclerView>()
if (view is EpoxyRecyclerView) {
recyclerViews.add(view)
} else if (view is ViewGroup) {
for (i in 0 until view.childCount) {
recyclerViews.addAll(findAllRecyclerViews(view.getChildAt(i)))
}
}
return recyclerViews
}
}

View File

@@ -1,100 +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.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.store.data.model.ViewState
import com.aurora.store.data.model.ViewState.Loading.getDataAs
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.CategoryCarouselController
import com.aurora.store.view.epoxy.controller.GenericCarouselController
import com.aurora.store.viewmodel.subcategory.CategoryStreamViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class CategoryBrowseFragment :
BaseFragment<FragmentGenericWithToolbarBinding>(),
GenericCarouselController.Callbacks {
private val args: CategoryBrowseFragmentArgs by navArgs()
private val viewModel: CategoryStreamViewModel by activityViewModels()
private var streamBundle: StreamBundle? = StreamBundle()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val genericCarouselController = CategoryCarouselController(this)
// Toolbar
binding.toolbar.apply {
title = args.title
setNavigationOnClickListener { findNavController().navigateUp() }
}
// RecyclerView
binding.recycler.setController(genericCarouselController)
binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.observe(args.browseUrl)
}
})
viewModel.getStreamBundle(args.browseUrl)
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Loading -> {
genericCarouselController.setData(null)
}
is ViewState.Success<*> -> {
val stash = it.getDataAs<Map<String, StreamBundle>>()
streamBundle = stash[args.browseUrl]
genericCarouselController.setData(streamBundle)
}
else -> {}
}
}
}
override fun onHeaderClicked(streamCluster: StreamCluster) {
openStreamBrowseFragment(streamCluster)
}
override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(args.browseUrl, streamCluster)
}
override fun onAppClick(app: App) {
openDetailsFragment(app.packageName)
}
override fun onAppLongClick(app: App) {
}
}

View File

@@ -1,104 +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.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.Category
import com.aurora.store.CategoryStash
import com.aurora.store.data.model.ViewState
import com.aurora.store.data.model.ViewState.Empty.getDataAs
import com.aurora.store.databinding.FragmentGenericRecyclerBinding
import com.aurora.store.view.epoxy.views.CategoryViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.category.CategoryViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class CategoryFragment : BaseFragment<FragmentGenericRecyclerBinding>() {
private val viewModel: CategoryViewModel by viewModels()
companion object {
@JvmStatic
fun newInstance(pageType: Int): CategoryFragment = CategoryFragment().apply {
arguments = Bundle().apply {
putInt(Constants.PAGE_TYPE, pageType)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var pageType = 0
val bundle = arguments
if (bundle != null) {
pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
}
when (pageType) {
0 -> viewModel.getCategoryList(Category.Type.APPLICATION)
1 -> viewModel.getCategoryList(Category.Type.GAME)
}
val categoryType = when (pageType) {
1 -> Category.Type.GAME
else -> Category.Type.APPLICATION
}
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Success<*> -> {
val stash = it.getDataAs<CategoryStash>()
updateController(stash[categoryType])
}
else -> {
updateController(emptyList())
}
}
}
}
fun updateController(categories: List<Category>?) {
binding.recycler.withModels {
if (categories == null) {
for (i in 1..10) {
add(
AppListViewShimmerModel_()
.id(i)
)
}
} else {
setFilterDuplicates(true)
categories.forEach {
add(
CategoryViewModel_()
.id(it.title)
.category(it)
.click { _ -> openCategoryBrowseFragment(it) }
)
}
}
}
}
}

View File

@@ -1,109 +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.commons
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.gplayapi.data.models.StreamCluster
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.browse.ExpandedStreamBrowseViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ExpandedStreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
private val args: ExpandedStreamBrowseFragmentArgs by navArgs()
private val viewModel: ExpandedStreamBrowseViewModel by viewModels()
private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener
private lateinit var cluster: StreamCluster
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Toolbar
binding.toolbar.apply {
title = args.title
setNavigationOnClickListener { findNavController().navigateUp() }
}
viewModel.liveData.observe(viewLifecycleOwner) {
if (!::cluster.isInitialized) attachRecycler()
cluster = it
updateController(cluster)
updateTitle(cluster)
}
viewModel.getInitialCluster(args.expandedStreamUrl)
updateController(null)
}
private fun updateTitle(streamCluster: StreamCluster) {
if (streamCluster.clusterTitle.isNotEmpty()) {
binding.toolbar.title = streamCluster.clusterTitle
}
}
private fun attachRecycler() {
endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.next()
}
}
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
}
private fun updateController(streamCluster: StreamCluster?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (streamCluster == null) {
for (i in 1..6) {
add(
AppListViewShimmerModel_()
.id(i)
)
}
} else {
streamCluster.clusterAppList.forEach {
add(
AppListViewModel_()
.id(it.packageName.hashCode())
.app(it)
.click { _ -> openDetailsFragment(it.packageName) }
)
}
if (streamCluster.hasNext()) {
add(
AppProgressViewModel_()
.id("progress")
)
}
}
}
}
}

View File

@@ -1,113 +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.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.StreamContract.Category
import com.aurora.gplayapi.helpers.contracts.StreamContract.Type
import com.aurora.store.HomeStash
import com.aurora.store.data.model.ViewState
import com.aurora.store.data.model.ViewState.Loading.getDataAs
import com.aurora.store.databinding.FragmentForYouBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.GenericCarouselController
import com.aurora.store.viewmodel.homestream.StreamViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ForYouFragment :
BaseFragment<FragmentForYouBinding>(),
GenericCarouselController.Callbacks {
private val viewModel: StreamViewModel by activityViewModels()
private var category: Category = Category.APPLICATION
private var streamBundle: StreamBundle? = StreamBundle()
companion object {
@JvmStatic
fun newInstance(pageType: Int): ForYouFragment = ForYouFragment().apply {
arguments = Bundle().apply {
putInt(Constants.PAGE_TYPE, pageType)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val genericCarouselController = GenericCarouselController(this)
var pageType = 0
val bundle = arguments
if (bundle != null) {
pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
}
category = if (pageType == 0) Category.APPLICATION else Category.GAME
binding.recycler.setController(genericCarouselController)
binding.recycler.addOnScrollListener(
object : EndlessRecyclerOnScrollListener(visibleThreshold = 4) {
override fun onLoadMore(currentPage: Int) {
viewModel.observe(category, Type.HOME)
}
}
)
viewModel.getStreamBundle(category, Type.HOME)
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Loading -> {
genericCarouselController.setData(null)
}
is ViewState.Success<*> -> {
val stash = it.getDataAs<HomeStash>()
streamBundle = stash[category]
genericCarouselController.setData(streamBundle)
}
else -> {}
}
}
}
override fun onHeaderClicked(streamCluster: StreamCluster) {
openStreamBrowseFragment(streamCluster)
}
override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(category, streamCluster)
}
override fun onAppClick(app: App) {
openDetailsFragment(app.packageName)
}
override fun onAppLongClick(app: App) {
}
}

View File

@@ -1,27 +0,0 @@
package com.aurora.store.view.ui.commons
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.aurora.store.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ForceRestartDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.force_restart_title)
.setMessage(R.string.force_restart_summary)
.setPositiveButton(getString(R.string.action_restart)) { _, _ ->
ProcessPhoenix.triggerRebirth(requireContext())
}
.create()
override fun onResume() {
super.onResume()
dialog?.setCancelable(false)
}
}

View File

@@ -1,460 +0,0 @@
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.BorderStroke
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.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.layout.wrapContentWidth
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.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEach
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.aurora.Constants
import com.aurora.Constants.URL_TOS
import com.aurora.extensions.browse
import com.aurora.extensions.getStyledAttributeColor
import com.aurora.extensions.navigate
import com.aurora.extensions.setAppTheme
import com.aurora.store.MR
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.compose.theme.AuroraTheme
import com.aurora.store.util.Preferences
import com.aurora.store.viewmodel.commons.MoreViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MoreDialogFragment : DialogFragment() {
private val viewModel: MoreViewModel by viewModels()
private var primaryColor: Color = Color.White
private var onPrimaryColor: Color = Color.Black
private var secondaryColor: Color = Color.White
private var onSecondaryColor: Color = Color.Black
private abstract class Option(
@StringRes open val title: Int,
@DrawableRes open val icon: Int
)
private data class ViewOption(
override val title: Int,
override val icon: Int,
@IdRes val destinationID: Int
) : Option(title, icon)
private data class ComposeOption(
override val title: Int,
override val icon: Int,
val screen: Screen
) : Option(title, icon)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
MaterialAlertDialogBuilder(requireContext())
.setView(customDialogView(requireContext()))
.create()
private fun customDialogView(context: Context): ComposeView = ComposeView(context).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
AuroraTheme {
primaryColor =
Color(requireContext().getStyledAttributeColor(MR.colorSurface))
onPrimaryColor =
Color(requireContext().getStyledAttributeColor(MR.colorOnSurface))
secondaryColor =
Color(requireContext().getStyledAttributeColor(MR.colorSecondaryContainer))
onSecondaryColor =
Color(
requireContext().getStyledAttributeColor(MR.colorOnSecondaryContainer)
)
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = primaryColor)
.verticalScroll(rememberScrollState())
.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(
4.dp,
Alignment.CenterVertically
)
) {
AppBar(onBackgroundColor = onPrimaryColor)
AccountHeader(
backgroundColor = secondaryColor,
onBackgroundColor = onSecondaryColor
)
Column(
modifier = Modifier
.clip(
RoundedCornerShape(
topStart = 2.dp,
topEnd = 2.dp,
bottomStart = 25.dp,
bottomEnd = 25.dp
)
)
.background(color = secondaryColor)
) {
getOptions().fastForEach { option ->
OptionItem(
option = option,
tintColor = onPrimaryColor,
textColor = onSecondaryColor,
onClick = {
when (option) {
is ViewOption -> {
findNavController().navigate(option.destinationID)
}
is ComposeOption -> context.navigate(option.screen)
}
}
)
}
}
getExtraOptions().fastForEach { option ->
OptionItem(
option = option,
tintColor = onPrimaryColor,
textColor = onPrimaryColor,
onClick = {
when (option) {
is ViewOption -> {
findNavController().navigate(option.destinationID)
}
is ComposeOption -> context.navigate(option.screen)
}
}
)
}
Footer(onPrimaryColor)
}
}
}
}
@Composable
fun AppBar(onBackgroundColor: Color) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp, 10.dp, 4.dp, 10.dp)
) {
ThreeStateIconButton(tint = onBackgroundColor)
Text(
modifier = Modifier.wrapContentWidth(),
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.titleMedium,
color = onBackgroundColor,
textAlign = TextAlign.Center
)
Image(
painter = painterResource(id = R.drawable.ic_cancel),
contentDescription = stringResource(id = R.string.action_cancel),
modifier = Modifier.clickable {
findNavController().navigateUp()
},
colorFilter = ColorFilter.tint(onBackgroundColor)
)
}
}
@Composable
fun Footer(tintColor: Color) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterHorizontally)
) {
TextButton(onClick = { requireContext().browse(Constants.URL_POLICY) }) {
Text(
text = stringResource(id = R.string.privacy_policy_title),
fontWeight = FontWeight.Light,
color = tintColor,
fontSize = 12.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Text(text = "", color = tintColor)
TextButton(onClick = { requireContext().browse(URL_TOS) }) {
Text(
text = stringResource(id = R.string.menu_terms),
fontWeight = FontWeight.Light,
color = tintColor,
fontSize = 12.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun AccountHeader(backgroundColor: Color, onBackgroundColor: Color = Color.White) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(
RoundedCornerShape(
topStart = 25.dp,
topEnd = 25.dp,
bottomStart = 2.dp,
bottomEnd = 2.dp
)
)
.background(color = backgroundColor)
.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)
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(
if (viewModel.authProvider.isAnonymous) {
R.mipmap.ic_launcher
} else {
viewModel.authProvider.authData?.userProfile?.artwork?.url
}
)
.crossfade(true)
.build(),
contentDescription = stringResource(id = R.string.title_account_manager),
placeholder = painterResource(R.drawable.ic_account),
contentScale = ContentScale.Crop,
modifier = Modifier
.requiredSize(36.dp)
.clip(CircleShape)
)
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
Text(
text = if (viewModel.authProvider.isAnonymous) {
stringResource(R.string.account_anonymous)
} else {
viewModel.authProvider.authData?.userProfile?.name
?: stringResource(R.string.status_unavailable)
},
fontWeight = FontWeight.Normal,
color = onBackgroundColor,
fontSize = 15.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = if (viewModel.authProvider.isAnonymous) {
stringResource(R.string.account_anonymous_email)
} else {
viewModel.authProvider.authData?.userProfile?.email
?: stringResource(R.string.status_unavailable)
},
fontWeight = FontWeight.Light,
color = onBackgroundColor,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
OutlinedButton(
onClick = { requireContext().navigate(Screen.Accounts) },
shape = RoundedCornerShape(12.dp),
border = BorderStroke(
1.dp,
Color(
requireContext().getStyledAttributeColor(
androidx.appcompat.R.attr.colorControlHighlight
)
)
),
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.manage_account),
color = onBackgroundColor,
fontWeight = FontWeight.Normal,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun OptionItem(
option: Option,
tintColor: Color = Color.Blue,
textColor: Color = Color.Black,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick() }
.padding(12.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 = tintColor),
modifier = Modifier
.padding(horizontal = 10.dp)
.requiredSize(23.dp)
)
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = option.title),
color = textColor,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
fun ThreeStateIconButton(tint: Color = Color.White) {
var currentState by remember {
mutableStateOf(
Preferences.getInteger(
requireContext(),
Preferences.PREFERENCE_THEME_STYLE
).let {
State.entries.getOrNull(it)
} ?: State.Auto
)
}
val iconRes = when (currentState) {
State.Light -> R.drawable.ic_light
State.Dark -> R.drawable.ic_dark
else -> R.drawable.ic_auto
}
Image(
painter = painterResource(id = iconRes),
contentDescription = null,
modifier = Modifier.clickable {
currentState = when (currentState) {
State.Light -> State.Dark
State.Dark -> State.Auto
State.Auto -> State.Light
}
Preferences.putInteger(
requireContext(),
Preferences.PREFERENCE_THEME_STYLE,
currentState.value
)
setAppTheme(currentState.value)
findNavController().navigateUp()
},
colorFilter = ColorFilter.tint(tint)
)
}
enum class State(val value: Int) {
Auto(0),
Light(1),
Dark(2)
}
private fun getOptions(): List<Option> = listOf(
ComposeOption(
title = R.string.title_apps_games,
icon = R.drawable.ic_apps,
screen = Screen.Installed
),
ComposeOption(
title = R.string.title_blacklist_manager,
icon = R.drawable.ic_blacklist,
screen = Screen.Blacklist
),
ComposeOption(
title = R.string.title_favourites_manager,
icon = R.drawable.ic_favorite_unchecked,
screen = Screen.Favourite
),
ComposeOption(
title = R.string.title_spoof_manager,
icon = R.drawable.ic_spoof,
screen = Screen.Spoof
)
)
private fun getExtraOptions(): List<Option> = listOf(
ViewOption(
title = R.string.title_settings,
icon = R.drawable.ic_menu_settings,
destinationID = R.id.settingsFragment
),
ComposeOption(
title = R.string.title_about,
icon = R.drawable.ic_menu_about,
screen = Screen.About
)
)
}

View File

@@ -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.ui.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.aurora.Constants
import com.aurora.store.R
import com.aurora.store.databinding.FragmentTopChartBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class TopChartContainerFragment : BaseFragment<FragmentTopChartBinding>() {
companion object {
@JvmStatic
fun newInstance(chartType: Int): TopChartContainerFragment =
TopChartContainerFragment().apply {
arguments = Bundle().apply {
putInt(Constants.TOP_CHART_TYPE, chartType)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var chartType = 0
val bundle = arguments
if (bundle != null) {
chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0)
}
// ViewPager
binding.pager.adapter =
ViewPagerAdapter(childFragmentManager, viewLifecycleOwner.lifecycle, chartType)
binding.topTabGroup.setOnCheckedStateChangeListener { _, checkedIds ->
when (checkedIds[0]) {
R.id.tab_top_free -> binding.pager.setCurrentItem(0, true)
R.id.tab_top_grossing -> binding.pager.setCurrentItem(1, true)
R.id.tab_trending -> binding.pager.setCurrentItem(2, true)
R.id.tab_top_paid -> binding.pager.setCurrentItem(3, true)
}
}
binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
when (position) {
0 -> binding.topTabGroup.check(R.id.tab_top_free)
1 -> binding.topTabGroup.check(R.id.tab_top_grossing)
2 -> binding.topTabGroup.check(R.id.tab_trending)
3 -> binding.topTabGroup.check(R.id.tab_top_paid)
}
}
})
}
override fun onDestroyView() {
binding.pager.adapter = null
super.onDestroyView()
}
internal class ViewPagerAdapter(
fragment: FragmentManager,
lifecycle: Lifecycle,
chartType: Int
) :
FragmentStateAdapter(fragment, lifecycle) {
private val tabFragments: MutableList<TopChartFragment> = mutableListOf(
TopChartFragment.newInstance(chartType, 0),
TopChartFragment.newInstance(chartType, 1),
TopChartFragment.newInstance(chartType, 2),
TopChartFragment.newInstance(chartType, 3)
)
override fun createFragment(position: Int): Fragment = tabFragments[position]
override fun getItemCount(): Int = tabFragments.size
}
}

View File

@@ -1,138 +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.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type
import com.aurora.store.TopChartStash
import com.aurora.store.data.model.ViewState
import com.aurora.store.data.model.ViewState.Empty.getDataAs
import com.aurora.store.databinding.FragmentTopContainerBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.topchart.TopChartViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class TopChartFragment : BaseFragment<FragmentTopContainerBinding>() {
private val viewModel: TopChartViewModel by activityViewModels()
private var streamCluster: StreamCluster? = StreamCluster()
companion object {
@JvmStatic
fun newInstance(chartType: Int, chartCategory: Int): TopChartFragment =
TopChartFragment().apply {
arguments = Bundle().apply {
putInt(Constants.TOP_CHART_TYPE, chartType)
putInt(Constants.TOP_CHART_CATEGORY, chartCategory)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var type = 0
var category = 0
val bundle = arguments
if (bundle != null) {
type = bundle.getInt(Constants.TOP_CHART_TYPE, 0)
category = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0)
}
val chartType = when (type) {
1 -> Type.GAME
else -> Type.APPLICATION
}
val chartCategory = when (category) {
1 -> Chart.TOP_GROSSING
2 -> Chart.MOVERS_SHAKERS
3 -> Chart.TOP_SELLING_PAID
else -> Chart.TOP_SELLING_FREE
}
binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.nextCluster(chartType, chartCategory)
}
})
updateController(null)
viewModel.getStreamCluster(chartType, chartCategory)
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Loading, is ViewState.Error -> {
updateController(null)
}
is ViewState.Success<*> -> {
val stash = it.getDataAs<TopChartStash>()
streamCluster = stash[chartType]?.get(chartCategory)
updateController(streamCluster)
}
else -> {}
}
}
}
private fun updateController(streamCluster: StreamCluster?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (streamCluster == null) {
for (i in 1..6) {
add(
AppListViewShimmerModel_()
.id(i)
)
}
} else {
streamCluster.clusterAppList.forEach { app ->
add(
AppListViewModel_()
.id(app.id)
.app(app)
.click { _ -> openDetailsFragment(app.packageName) }
)
}
if (streamCluster.hasNext()) {
add(
AppProgressViewModel_()
.id("progress")
)
}
}
}
}
}

View File

@@ -1,107 +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 coil3.load
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamCluster
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.GenericCarouselController
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.details.DevProfileViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DevProfileFragment :
BaseFragment<FragmentDevProfileBinding>(),
GenericCarouselController.Callbacks {
private val args: DevProfileFragmentArgs by navArgs()
private val viewModel: DevProfileViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Toolbar
binding.toolbar.apply {
title =
if (args.title.isNullOrBlank()) {
getString(R.string.details_dev_profile)
} else {
args.title
}
setNavigationOnClickListener { findNavController().navigateUp() }
}
// RecyclerView
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Empty -> {
}
is ViewState.Loading -> {
}
is ViewState.Error -> {
}
is ViewState.Status -> {
}
is ViewState.Success<*> -> {
(it.data as DevStream).apply {
binding.toolbar.title = title
binding.txtDevName.text = title
binding.txtDevDescription.text = description
binding.imgIcon.load(imgUrl)
binding.viewFlipper.displayedChild = 0
}
}
}
}
binding.viewFlipper.displayedChild = 1
viewModel.getStreamBundle(args.devId)
}
override fun onHeaderClicked(streamCluster: StreamCluster) {
openStreamBrowseFragment(streamCluster.clusterBrowseUrl, streamCluster.clusterTitle)
}
override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(streamCluster)
}
override fun onAppClick(app: App) {
openDetailsFragment(app.packageName)
}
override fun onAppLongClick(app: App) {
}
}

View File

@@ -1,147 +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.games
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.aurora.extensions.navigate
import com.aurora.store.MobileNavigationDirections
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.databinding.FragmentAppsGamesBinding
import com.aurora.store.util.Preferences
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.view.ui.commons.CategoryFragment
import com.aurora.store.view.ui.commons.ForYouFragment
import com.aurora.store.view.ui.commons.TopChartContainerFragment
import com.aurora.store.viewmodel.games.GamesContainerViewModel
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class GamesContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
private val viewModel: GamesContainerViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Adjust FAB margins for edgeToEdge display
ViewCompat.setOnApplyWindowInsetsListener(binding.searchFab) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
binding.searchFab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.bottom + resources.getDimensionPixelSize(R.dimen.margin_large)
}
WindowInsetsCompat.CONSUMED
}
// Toolbar
binding.toolbar.apply {
title = getString(R.string.title_games)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_download_manager -> {
requireContext().navigate(Screen.Downloads)
}
R.id.menu_more -> {
findNavController().navigate(
MobileNavigationDirections.actionGlobalMoreDialogFragment()
)
}
}
true
}
}
// ViewPager
val isForYouEnabled = Preferences.getBoolean(
requireContext(),
Preferences.PREFERENCE_FOR_YOU
)
binding.pager.adapter = ViewPagerAdapter(
childFragmentManager,
viewLifecycleOwner.lifecycle,
!viewModel.authProvider.isAnonymous,
isForYouEnabled
)
binding.pager.isUserInputEnabled = false
val tabTitles: MutableList<String> = mutableListOf<String>().apply {
if (isForYouEnabled) {
add(getString(R.string.tab_for_you))
}
add(getString(R.string.tab_top_charts))
add(getString(R.string.tab_categories))
}
TabLayoutMediator(
binding.tabLayout,
binding.pager,
true
) { tab: TabLayout.Tab, position: Int ->
tab.text = tabTitles[position]
}.attach()
binding.searchFab.setOnClickListener {
requireContext().navigate(Screen.Search)
}
}
override fun onDestroyView() {
binding.pager.adapter = null
super.onDestroyView()
}
internal class ViewPagerAdapter(
fragment: FragmentManager,
lifecycle: Lifecycle,
private val isGoogleAccount: Boolean,
private val isForYouEnabled: Boolean
) :
FragmentStateAdapter(fragment, lifecycle) {
private val tabFragments: MutableList<Fragment> = mutableListOf<Fragment>().apply {
if (isForYouEnabled) {
add(ForYouFragment.newInstance(1))
}
add(TopChartContainerFragment.newInstance(1))
add(CategoryFragment.newInstance(1))
}
override fun createFragment(position: Int): Fragment = tabFragments[position]
override fun getItemCount(): Int = tabFragments.size
}
}

View File

@@ -1,49 +0,0 @@
package com.aurora.store.view.ui.preferences
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.aurora.store.data.providers.PermissionProvider
import com.aurora.store.view.custom.preference.M3EditTextPreference
import com.aurora.store.view.custom.preference.M3ListPreference
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
lateinit var permissionProvider: PermissionProvider
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionProvider = PermissionProvider(this)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
when (preference) {
is EditTextPreference -> {
val dialogFragment = M3EditTextPreference.newInstance(preference.getKey())
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(
parentFragmentManager,
M3EditTextPreference.PREFERENCE_DIALOG_FRAGMENT_TAG
)
}
is ListPreference -> {
val dialogFragment = M3ListPreference.newInstance(preference.getKey())
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(
parentFragmentManager,
M3ListPreference.PREFERENCE_DIALOG_FRAGMENT_TAG
)
}
else -> super.onDisplayPreferenceDialog(preference)
}
}
override fun onDestroy() {
permissionProvider.unregister()
super.onDestroy()
}
}

View File

@@ -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.ui.preferences
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.aurora.extensions.navigate
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.data.model.PermissionType
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_settings, rootKey)
findPreference<Preference>("pref_perms")?.setOnPreferenceClickListener {
requireContext().navigate(
Screen.PermissionRationale(requiredPermissions = PermissionType.entries.toSet())
)
true
}
findPreference<Preference>("pref_install")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.installationPreference)
true
}
findPreference<Preference>("pref_ui")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.UIPreference)
true
}
findPreference<Preference>("pref_network")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.networkPreference)
true
}
findPreference<Preference>("pref_updates")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.updatesPreference)
true
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Toolbar>(R.id.toolbar)?.apply {
title = getString(R.string.title_settings)
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
}

View File

@@ -1,66 +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.preferences
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.core.net.toUri
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import com.aurora.extensions.isTAndAbove
import com.aurora.store.R
import dagger.hilt.android.AndroidEntryPoint
import java.util.Locale
@AndroidEntryPoint
class UIPreference : BasePreferenceFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_ui, rootKey)
findPreference<Preference>("PREFERENCE_APP_LANGUAGE")?.apply {
if (isTAndAbove) {
summary = Locale.getDefault().displayName
setOnPreferenceClickListener {
startActivity(
Intent(Settings.ACTION_APP_LOCALE_SETTINGS).apply {
data = ("package:" + requireContext().packageName).toUri()
}
)
true
}
} else {
isVisible = false
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Toolbar>(R.id.toolbar)?.apply {
title = getString(R.string.pref_ui_title)
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
}

View File

@@ -1,81 +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.preferences.installation
import android.app.admin.DevicePolicyManager
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.core.content.getSystemService
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.aurora.extensions.navigate
import com.aurora.extensions.showDialog
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLATION_DEVICE_OWNER
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class InstallationPreference : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_installation, rootKey)
findPreference<Preference>(PREFERENCE_INSTALLER_ID)?.apply {
setOnPreferenceClickListener {
requireContext().navigate(Screen.Installer)
true
}
}
findPreference<Preference>(PREFERENCE_INSTALLATION_DEVICE_OWNER)?.apply {
val packageName = context.packageName
val devicePolicyManager = context.getSystemService<DevicePolicyManager>()
isVisible = devicePolicyManager?.isDeviceOwnerApp(packageName) ?: false
setOnPreferenceClickListener {
context.showDialog(
context.getString(R.string.pref_clear_device_owner_title),
context.getString(R.string.pref_clear_device_owner_desc),
{ _: DialogInterface, _: Int ->
@Suppress("DEPRECATION")
devicePolicyManager!!.clearDeviceOwnerApp(packageName)
activity?.recreate()
},
{ dialog: DialogInterface, _: Int -> dialog.dismiss() }
)
true
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Toolbar>(R.id.toolbar)?.apply {
title = getString(R.string.title_installation)
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
}

View File

@@ -1,81 +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.preferences.network
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.aurora.extensions.navigate
import com.aurora.extensions.runOnUiThread
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_MICROG_AUTH
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_URL
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
import com.aurora.store.util.save
import com.aurora.store.view.ui.preferences.BasePreferenceFragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class NetworkPreference : BasePreferenceFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_network, rootKey)
findPreference<Preference>(Preferences.PREFERENCE_DISPENSER_URLS)?.apply {
setOnPreferenceClickListener {
requireContext().navigate(Screen.Dispenser)
true
}
}
findPreference<Preference>(PREFERENCE_PROXY_URL)?.setOnPreferenceClickListener { _ ->
findNavController().navigate(R.id.proxyURLDialog)
false
}
findPreference<Preference>(PREFERENCE_VENDING_VERSION)?.let {
it.setOnPreferenceChangeListener { _, newValue ->
save(PREFERENCE_VENDING_VERSION, Integer.parseInt(newValue.toString()))
runOnUiThread {
requireContext().toast(R.string.insecure_anonymous_apply)
}
true
}
}
findPreference<SwitchPreferenceCompat>(PREFERENCE_MICROG_AUTH)?.isVisible =
PackageUtil.hasSupportedMicroGVariant(requireContext())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Toolbar>(R.id.toolbar)?.apply {
title = getString(R.string.pref_network_title)
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
}

View File

@@ -1,97 +0,0 @@
package com.aurora.store.view.ui.preferences.network
import android.app.Dialog
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.aurora.extensions.showKeyboard
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_INFO
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_URL
import com.aurora.store.util.remove
import com.aurora.store.util.save
import com.aurora.store.viewmodel.preferences.ProxyURLViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ProxyURLDialog : DialogFragment() {
private val viewModel: ProxyURLViewModel by viewModels()
private val currentProxyUrl: String
get() = Preferences.getString(requireContext(), PREFERENCE_PROXY_URL)
private val textInputLayout: TextInputLayout?
get() = dialog?.findViewById(R.id.textInputLayout)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = layoutInflater.inflate(R.layout.dialog_text_input_edit_text, null)
val alertDialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.pref_network_proxy_url)
.setMessage(R.string.pref_network_proxy_url_message)
.setView(view)
.setPositiveButton(getString(R.string.set), null)
.setNeutralButton(getString(R.string.disable), null)
.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dialog?.dismiss() }
.create()
alertDialog.setOnShowListener {
val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).apply {
isEnabled = currentProxyUrl.isNotBlank()
setOnClickListener { saveProxyUrl() }
}
textInputLayout?.editText?.doOnTextChanged { text, _, _, _ ->
positiveButton.isEnabled = !text.isNullOrBlank()
}
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).apply {
isEnabled = currentProxyUrl.isNotBlank()
setOnClickListener { deleteProxyUrl() }
}
}
return alertDialog
}
override fun onResume() {
super.onResume()
textInputLayout?.editText?.apply {
setText(currentProxyUrl)
showKeyboard()
}
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
private fun saveProxyUrl() {
val url = textInputLayout?.editText?.text?.toString()?.trim()
if (url.isNullOrEmpty()) {
toast(R.string.toast_proxy_invalid)
return
}
val proxyInfo = CommonUtil.parseProxyUrl(url)
if (proxyInfo != null) {
save(PREFERENCE_PROXY_URL, url)
save(PREFERENCE_PROXY_INFO, viewModel.json.encodeToString(proxyInfo))
toast(R.string.toast_proxy_success)
findNavController().navigate(R.id.forceRestartDialog)
return
} else {
toast(R.string.toast_proxy_failed)
}
}
private fun deleteProxyUrl() {
remove(PREFERENCE_PROXY_URL)
remove(PREFERENCE_PROXY_INFO)
toast(R.string.toast_proxy_disabled)
findNavController().navigate(R.id.forceRestartDialog)
}
}

View File

@@ -1,161 +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.preferences.updates
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import com.aurora.store.R
import com.aurora.store.data.model.PermissionType
import com.aurora.store.data.model.UpdateMode
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS
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_UPDATES_AUTO
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
import com.aurora.store.util.save
import com.aurora.store.view.ui.preferences.BasePreferenceFragment
import com.aurora.store.viewmodel.all.UpdatesViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class UpdatesPreference : BasePreferenceFragment() {
private val viewModel: UpdatesViewModel by viewModels()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_updates, rootKey)
val updatesEnabled = Preferences.getInteger(requireContext(), PREFERENCE_UPDATES_AUTO) != 0
findPreference<ListPreference>(PREFERENCE_UPDATES_AUTO)?.setOnPreferenceChangeListener {
_,
newValue
->
when (UpdateMode.entries[newValue.toString().toInt()]) {
UpdateMode.DISABLED -> {
handleAutoUpdateDependentPrefs(false)
viewModel.updateHelper.cancelAutomatedCheck()
requireContext().save(PREFERENCE_UPDATES_AUTO, 0)
true
}
UpdateMode.CHECK_AND_NOTIFY -> {
if (isGranted(requireContext(), PermissionType.POST_NOTIFICATIONS)) {
handleAutoUpdateDependentPrefs(true)
viewModel.updateHelper.scheduleAutomatedCheck()
true
} else {
permissionProvider.request(PermissionType.POST_NOTIFICATIONS) {
if (it) {
handleAutoUpdateDependentPrefs(true)
requireContext().save(PREFERENCE_UPDATES_AUTO, 1)
viewModel.updateHelper.scheduleAutomatedCheck()
activity?.recreate()
}
}
false
}
}
UpdateMode.CHECK_AND_INSTALL -> {
if (isGranted(requireContext(), PermissionType.DOZE_WHITELIST)) {
handleAutoUpdateDependentPrefs(true)
viewModel.updateHelper.scheduleAutomatedCheck()
true
} else {
permissionProvider.request(PermissionType.DOZE_WHITELIST) {
if (it) {
handleAutoUpdateDependentPrefs(true)
requireContext().save(PREFERENCE_UPDATES_AUTO, 2)
viewModel.updateHelper.scheduleAutomatedCheck()
activity?.recreate()
}
}
false
}
}
else -> false
}
}
handleAutoUpdateDependentPrefs(updatesEnabled)
findPreference<SwitchPreferenceCompat>(PREFERENCE_FILTER_AURORA_ONLY)
?.setOnPreferenceChangeListener { _, newValue ->
findPreference<SwitchPreferenceCompat>(PREFERENCE_FILTER_FDROID)?.isEnabled =
!newValue.toString().toBoolean()
viewModel.updateHelper.checkUpdatesNow()
true
}
findPreference<SwitchPreferenceCompat>(PREFERENCE_FILTER_FDROID)?.apply {
isEnabled = !Preferences.getBoolean(requireContext(), PREFERENCE_FILTER_AURORA_ONLY)
setOnPreferenceChangeListener { _, _ ->
viewModel.updateHelper.checkUpdatesNow()
true
}
}
findPreference<SwitchPreferenceCompat>(PREFERENCE_UPDATES_EXTENDED)
?.setOnPreferenceChangeListener { _, _ ->
viewModel.updateHelper.checkUpdatesNow()
true
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Toolbar>(R.id.toolbar)?.apply {
title = getString(R.string.title_updates)
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
private fun handleAutoUpdateDependentPrefs(enabled: Boolean) {
findPreference<Preference>(PREFERENCES_UPDATES_RESTRICTIONS)?.apply {
isEnabled = enabled
isVisible = enabled
setOnPreferenceClickListener {
findNavController().navigate(R.id.updatesRestrictionsDialog)
true
}
}
findPreference<SeekBarPreference>(PREFERENCE_UPDATES_CHECK_INTERVAL)?.apply {
isEnabled = enabled
isVisible = enabled
setOnPreferenceChangeListener { _, _ ->
viewModel.updateHelper.updateAutomatedCheck()
true
}
}
}
}

View File

@@ -1,72 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.view.ui.preferences.updates
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import com.aurora.store.R
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_METERED
import com.aurora.store.viewmodel.preferences.UpdatesRestrictionsViewModel
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class UpdatesRestrictionsDialog : DialogFragment() {
private val viewModel: UpdatesRestrictionsViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = layoutInflater.inflate(R.layout.dialog_auto_updates_restrictions, null)
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.pref_updates_restrictions_title)
.setMessage(R.string.pref_updates_restrictions_desc)
.setView(view)
.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dialog?.dismiss() }
.create()
}
override fun onResume() {
super.onResume()
context?.let { setupRestrictions(it) }
}
override fun onDestroy() {
viewModel.updateHelper.updateAutomatedCheck()
super.onDestroy()
}
private fun setupRestrictions(context: Context) {
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxMetered)?.apply {
isChecked =
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_METERED, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_METERED, isChecked)
}
}
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxIdle)?.apply {
isChecked = Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, isChecked)
}
}
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxBattery)?.apply {
isChecked =
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, isChecked)
}
}
}
}

View File

@@ -1,118 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
* Copyright (C) 2022, The Calyx Institute
*
* 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.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs
import com.aurora.extensions.openInfo
import com.aurora.extensions.toast
import com.aurora.store.AuroraApp
import com.aurora.store.R
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.databinding.SheetAppMenuBinding
import com.aurora.store.util.PackageUtil
import com.aurora.store.viewmodel.sheets.AppMenuViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AppMenuSheet : BaseDialogSheet<SheetAppMenuBinding>() {
companion object {
const val TAG = "APP_MENU_SHEET"
}
private val viewModel: AppMenuViewModel by viewModels()
private val args: AppMenuSheetArgs by navArgs()
private val exportMimeType = "application/zip"
private val requestDocumentCreation =
registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) {
if (it != null) {
viewModel.copyInstalledApp(requireContext(), args.app, it)
} else {
toast(R.string.failed_apk_export)
}
dismissAllowingStateLoss()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val isBlacklisted: Boolean = viewModel.blacklistProvider.isBlacklisted(args.app.packageName)
with(binding.navigationView) {
// Switch strings for Add/Remove Blacklist
val blackListMenu: MenuItem = menu.findItem(R.id.action_blacklist)
blackListMenu.setTitle(
if (isBlacklisted) {
R.string.action_whitelist
} else {
R.string.action_blacklist_add
}
)
// Show/Hide actions based on installed status
val installed = PackageUtil.isInstalled(requireContext(), args.app.packageName)
menu.findItem(R.id.action_uninstall).isVisible = installed
menu.findItem(R.id.action_local).isVisible = installed
setNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.action_blacklist -> {
if (isBlacklisted) {
viewModel.blacklistProvider.whitelist(args.app.packageName)
requireContext().toast(R.string.toast_apk_whitelisted)
} else {
viewModel.blacklistProvider.blacklist(args.app.packageName)
requireContext().toast(R.string.toast_apk_blacklisted)
}
dismissAllowingStateLoss()
AuroraApp.events.send(
BusEvent.Blacklisted(args.app.packageName)
)
}
R.id.action_local -> {
requestDocumentCreation.launch("${args.app.packageName}.zip")
}
R.id.action_uninstall -> {
AppInstaller.uninstall(requireContext(), args.app.packageName)
dismissAllowingStateLoss()
}
R.id.action_info -> {
requireContext().openInfo(args.app.packageName)
dismissAllowingStateLoss()
}
}
false
}
}
}
}

View File

@@ -1,70 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
* Copyright (C) 2022, The Calyx Institute
*
* 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.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.aurora.store.data.providers.PermissionProvider
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import java.lang.reflect.ParameterizedType
abstract class BaseDialogSheet<ViewBindingType : ViewBinding> : BottomSheetDialogFragment() {
private var _binding: ViewBindingType? = null
protected val binding get() = _binding!!
lateinit var permissionProvider: PermissionProvider
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionProvider = PermissionProvider(this)
}
@Suppress("UNCHECKED_CAST")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val type = (javaClass.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<ViewBindingType>
val method = type.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
)
_binding = method.invoke(null, inflater, container, false) as ViewBindingType
return binding.root
}
override fun onDestroyView() {
permissionProvider.unregister()
_binding = null
super.onDestroyView()
}
}

View File

@@ -1,56 +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.sheets
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.view.View
import coil3.load
import coil3.request.transformations
import coil3.transform.CircleCropTransformation
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.databinding.SheetDeviceMiuiBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DeviceMiuiSheet : BaseDialogSheet<SheetDeviceMiuiBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.imgIcon.load(R.drawable.ic_xiaomi_logo) {
transformations(CircleCropTransformation())
}
binding.btnPrimary.setOnClickListener {
try {
startActivity(Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS))
} catch (_: Exception) {
toast(R.string.toast_developer_setting_failed)
}
}
binding.btnSecondary.setOnClickListener {
dismissAllowingStateLoss()
}
}
}

View File

@@ -1,60 +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.sheets
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.View
import com.aurora.extensions.TAG
import com.aurora.extensions.isQAndAbove
import com.aurora.store.databinding.SheetNetworkBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class NetworkDialogSheet : BaseDialogSheet<SheetNetworkBinding>() {
companion object {
const val TAG = "NetworkDialogSheet"
@JvmStatic
fun newInstance(): NetworkDialogSheet = NetworkDialogSheet().apply {
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnAction.setOnClickListener {
if (isQAndAbove) {
startActivity(Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY))
} else {
try {
startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
} catch (exception: ActivityNotFoundException) {
Log.i(TAG, "Unable to launch wireless settings")
startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
}
}
}

View File

@@ -1,268 +0,0 @@
package com.aurora.store.view.ui.splash
import android.accounts.Account
import android.accounts.AccountManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
import com.aurora.extensions.TAG
import com.aurora.extensions.getPackageName
import com.aurora.extensions.navigate
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.data.model.AuthState
import com.aurora.store.databinding.FragmentSplashBinding
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
import com.aurora.store.util.Preferences.PREFERENCE_MICROG_AUTH
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.auth.AuthViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
abstract class BaseFlavouredSplashFragment : BaseFragment<FragmentSplashBinding>() {
val viewModel: AuthViewModel by activityViewModels()
val canLoginWithMicroG: Boolean
get() = PackageUtil.hasSupportedMicroGVariant(requireContext()) &&
Preferences.getBoolean(requireContext(), PREFERENCE_MICROG_AUTH, true)
val startForAccount =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val accountName = it.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
if (!accountName.isNullOrBlank()) {
requestAuthTokenForGoogle(accountName)
} else {
resetActions()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewBindingType = FragmentSplashBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (!Preferences.getBoolean(requireContext(), PREFERENCE_INTRO)) {
requireContext().navigate(Screen.Onboarding)
activity?.finish()
return
}
// Toolbar
binding.toolbar.apply {
setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_blacklist_manager -> {
requireContext().navigate(Screen.Blacklist)
}
R.id.menu_spoof_manager -> requireContext().navigate(Screen.Spoof)
R.id.menu_settings -> {
findNavController().navigate(R.id.settingsFragment)
}
R.id.menu_about -> requireContext().navigate(Screen.About)
}
true
}
}
attachActions()
viewLifecycleOwner.lifecycleScope.launch {
viewModel.authState.collectLatest {
when (it) {
AuthState.Init -> updateStatus(getString(R.string.session_init))
AuthState.Fetching -> {
updateStatus(getString(R.string.requesting_new_session))
}
AuthState.Valid -> {
val packageName =
requireActivity().intent.getPackageName(requireArguments())
if (packageName.isNullOrBlank()) {
navigateToDefaultTab()
} else {
requireArguments().remove("packageName")
openDetailsFragment(packageName)
}
}
AuthState.Available -> {
updateStatus(getString(R.string.session_verifying))
updateActionLayout(false)
}
AuthState.Unavailable -> {
updateStatus(getString(R.string.session_login))
updateActionLayout(true)
}
AuthState.SignedIn -> {
val packageName =
requireActivity().intent.getPackageName(requireArguments())
if (packageName.isNullOrBlank()) {
navigateToDefaultTab()
} else {
requireArguments().remove("packageName")
openDetailsFragment(packageName)
}
}
AuthState.SignedOut -> {
updateStatus(getString(R.string.session_scrapped))
updateActionLayout(true)
}
AuthState.Verifying -> {
updateStatus(getString(R.string.verifying_new_session))
}
is AuthState.PendingAccountManager -> {
requestAuthTokenForGoogle(it.email, it.token)
}
is AuthState.Failed -> {
updateStatus(it.status)
updateActionLayout(true)
resetActions()
}
}
}
}
}
private fun updateStatus(string: String?) {
activity?.runOnUiThread { binding.txtStatus.text = string }
}
private fun updateActionLayout(isVisible: Boolean) {
binding.layoutAction.isVisible = isVisible
binding.toolbar.isVisible = isVisible
}
private fun navigateToDefaultTab() {
val defaultDestination =
Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
val directions =
when (requireArguments().getInt("destinationId", defaultDestination)) {
R.id.updatesFragment -> {
requireArguments().remove("destinationId")
SplashFragmentDirections.actionSplashFragmentToUpdatesFragment()
}
1 -> SplashFragmentDirections.actionSplashFragmentToGamesContainerFragment()
2 -> SplashFragmentDirections.actionSplashFragmentToUpdatesFragment()
else -> SplashFragmentDirections.actionSplashFragmentToNavigationApps()
}
requireActivity().viewModelStore.clear() // Clear ViewModelStore to avoid bugs with logout
findNavController().navigate(directions)
}
private fun requestAuthTokenForGoogle(accountName: String, oldToken: String? = null) {
try {
if (oldToken != null) {
// Invalidate the old token before requesting a new one
AccountManager.get(requireContext()).invalidateAuthToken(
GOOGLE_ACCOUNT_TYPE,
oldToken
)
}
AccountManager.get(requireContext())
.getAuthToken(
Account(accountName, GOOGLE_ACCOUNT_TYPE),
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
Bundle().apply {
putString("overridePackage", PACKAGE_NAME_PLAY_STORE)
putByteArray(
"overrideCertificate",
Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
)
},
requireActivity(),
{
viewModel.buildGoogleAuthData(
accountName,
it.result.getString(AccountManager.KEY_AUTHTOKEN)!!,
AuthHelper.Token.AUTH
)
},
Handler(Looper.getMainLooper())
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to get authToken for Google login")
}
}
open fun attachActions() {
binding.btnAnonymous.addOnClickListener {
if (viewModel.authState.value != AuthState.Fetching) {
binding.btnAnonymous.updateProgress(true)
viewModel.buildAnonymousAuthData()
}
}
binding.btnGoogle.addOnClickListener {
if (viewModel.authState.value != AuthState.Fetching) {
binding.btnGoogle.updateProgress(true)
if (canLoginWithMicroG) {
Log.i(TAG, "Found supported microG, trying to request credentials")
val accountIntent = AccountManager.newChooseAccountIntent(
null,
null,
arrayOf(GOOGLE_ACCOUNT_TYPE),
null,
null,
null,
null
)
startForAccount.launch(accountIntent)
} else {
findNavController().navigate(R.id.googleFragment)
}
}
}
}
open fun resetActions() {
binding.btnGoogle.apply {
updateProgress(false)
isEnabled = true
}
binding.btnAnonymous.apply {
updateProgress(false)
isEnabled = true
}
}
}

View File

@@ -1,234 +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.updates
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.Constants
import com.aurora.extensions.browse
import com.aurora.extensions.navigate
import com.aurora.extensions.requiresObbDir
import com.aurora.store.MobileNavigationDirections
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.data.model.MinimalApp
import com.aurora.store.data.model.PermissionType
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.update.Update
import com.aurora.store.databinding.FragmentUpdatesBinding
import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_
import com.aurora.store.view.epoxy.views.app.AppUpdateViewModel_
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.all.UpdatesViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@AndroidEntryPoint
class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
private val viewModel: UpdatesViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Adjust FAB margins for edgeToEdge display
ViewCompat.setOnApplyWindowInsetsListener(binding.searchFab) { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
binding.searchFab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.bottom + resources.getDimensionPixelSize(R.dimen.margin_large)
}
WindowInsetsCompat.CONSUMED
}
// Toolbar
binding.toolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_download_manager -> {
requireContext().navigate(Screen.Downloads)
}
R.id.menu_more -> {
findNavController().navigate(
MobileNavigationDirections.actionGlobalMoreDialogFragment()
)
}
}
true
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.updates
.combine(viewModel.downloadsList) { uList, dList ->
uList?.associateWith { a ->
dList.find {
it.packageName == a.packageName && it.versionCode == a.versionCode
}
}
}.collectLatest { map ->
updateController(map)
viewModel.updateAllEnqueued = map?.values?.all {
it?.isRunning == true
} ?: false
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.fetchingUpdates.collect {
binding.swipeRefreshLayout.isRefreshing = it
if (it && viewModel.updates.value.isNullOrEmpty()) {
updateController(emptyMap())
}
}
}
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.fetchUpdates()
}
binding.searchFab.setOnClickListener {
requireContext().navigate(Screen.Search)
}
}
private fun updateController(appList: Map<Update, Download?>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (appList == null) {
for (i in 1..10) {
add(
AppListViewShimmerModel_()
.id(i)
)
}
} else {
if (appList.isEmpty()) {
add(
NoAppViewModel_()
.id("no_update")
.icon(R.drawable.ic_updates)
.message(R.string.details_no_updates)
.showAction(true)
.actionMessage(R.string.check_updates)
.actionCallback { _ -> viewModel.fetchUpdates() }
)
} else {
add(
UpdateHeaderViewModel_()
.id("header_all")
.title(
"${appList.size} " +
if (appList.size == 1) {
getString(R.string.update_available)
} else {
getString(R.string.updates_available)
}
)
.action(
if (viewModel.updateAllEnqueued) {
getString(R.string.action_cancel)
} else {
getString(R.string.action_update_all)
}
)
.click { _ ->
if (viewModel.updateAllEnqueued) {
cancelAll()
} else {
updateAll()
}
requestModelBuild()
}
)
for ((update, download) in appList) {
add(
AppUpdateViewModel_()
.id(update.packageName)
.update(update)
.download(download)
.click { _ ->
if (update.packageName == requireContext().packageName) {
requireContext().browse(Constants.GITLAB_URL)
} else {
openDetailsFragment(update.packageName)
}
}
.longClick { _ ->
openAppMenuSheet(MinimalApp.fromUpdate(update))
false
}
.positiveAction { _ -> updateSingle(update) }
.negativeAction { _ -> cancelSingle(update) }
)
}
}
}
}
}
private fun updateSingle(update: Update) {
if (update.fileList.requiresObbDir()) {
if (isGranted(requireContext(), PermissionType.STORAGE_MANAGER)) {
viewModel.download(update)
} else {
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
if (it) viewModel.download(update)
}
}
} else {
viewModel.download(update)
}
}
private fun updateAll() {
viewModel.updateAllEnqueued = true
if (viewModel.updates.value?.any { it.fileList.requiresObbDir() } == true) {
if (isGranted(requireContext(), PermissionType.STORAGE_MANAGER)) {
viewModel.downloadAll()
} else {
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
if (it) viewModel.downloadAll()
}
}
} else {
viewModel.downloadAll()
}
}
private fun cancelSingle(update: Update) {
viewModel.cancelDownload(update.packageName)
}
private fun cancelAll() {
viewModel.cancelAll()
}
}