mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-05-19 13:55:46 -04:00
Merge branch 'dev'
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -36,9 +36,10 @@ gradle-app.setting
|
||||
*.zip
|
||||
|
||||
app/release/*
|
||||
app/beta/*
|
||||
app/alpha/*
|
||||
app/nightly/*
|
||||
app/vanilla/*
|
||||
app/huawei/*
|
||||
app/preload/*
|
||||
|
||||
|
||||
#Exclude signing configurations & keystore
|
||||
|
||||
@@ -58,6 +58,8 @@ android {
|
||||
testInstrumentationRunnerArguments["disableAnalytics"] = "true"
|
||||
|
||||
buildConfigField("String", "EXODUS_API_KEY", "\"bbe6ebae4ad45a9cbacb17d69739799b8df2c7ae\"")
|
||||
|
||||
missingDimensionStrategy("device", "vanilla")
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -108,12 +110,32 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "device"
|
||||
|
||||
productFlavors {
|
||||
create("vanilla") {
|
||||
dimension = "device"
|
||||
}
|
||||
|
||||
create("huawei") {
|
||||
dimension = "device"
|
||||
versionNameSuffix = "-hw"
|
||||
}
|
||||
|
||||
// This flavor is only for preloaded devices / users who push the app to system
|
||||
create("preload") {
|
||||
dimension = "device"
|
||||
versionNameSuffix = "-preload"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
aidl = true
|
||||
compose = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_21.toString()
|
||||
}
|
||||
@@ -137,6 +159,15 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants(selector().all()) { variant ->
|
||||
val flavour = variant.flavorName
|
||||
if ((flavour == "huawei" || flavour == "preload") && variant.buildType == "nightly") {
|
||||
variant.enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="MissingTranslation" severity="ignore" />
|
||||
<issue id="ProtectedPermissions" severity="ignore" />
|
||||
<issue id="QueryAllPackagesPermission" severity="ignore" />
|
||||
<issue id="ScopedStorage" severity="ignore" />
|
||||
</lint>
|
||||
|
||||
7
app/src/huawei/AndroidManifest.xml
Normal file
7
app/src/huawei/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.onboarding
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
|
||||
import com.aurora.store.util.save
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
|
||||
override fun loadDefaultPreferences() {
|
||||
/*Filters*/
|
||||
save(PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
save(PREFERENCE_FILTER_FDROID, true)
|
||||
|
||||
/*Network*/
|
||||
save(PREFERENCE_DISPENSER_URLS, setOf())
|
||||
save(PREFERENCE_VENDING_VERSION, 0)
|
||||
|
||||
/*Customization*/
|
||||
save(PREFERENCE_THEME_STYLE, 0)
|
||||
save(PREFERENCE_DEFAULT_SELECTED_TAB, 0)
|
||||
save(PREFERENCE_FOR_YOU, true)
|
||||
save(PREFERENCE_SIMILAR, true)
|
||||
|
||||
/*Installer*/
|
||||
save(PREFERENCE_AUTO_DELETE, true)
|
||||
save(PREFERENCE_INSTALLER_ID, 0)
|
||||
|
||||
/*Updates*/
|
||||
save(PREFERENCE_UPDATES_EXTENDED, false)
|
||||
save(PREFERENCE_UPDATES_CHECK_INTERVAL, 3)
|
||||
}
|
||||
|
||||
override fun onboardingPages(): List<Fragment> {
|
||||
return listOf(
|
||||
WelcomeFragment(),
|
||||
PermissionsFragment.newInstance()
|
||||
)
|
||||
}
|
||||
|
||||
override fun setupAutoUpdates() {
|
||||
super.setupAutoUpdates()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
|
||||
override fun finishOnboarding() {
|
||||
super.finishOnboarding()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.splash
|
||||
|
||||
import com.aurora.extensions.hide
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SplashFragment : BaseFlavouredSplashFragment() {
|
||||
override fun attachActions() {
|
||||
super.attachActions()
|
||||
|
||||
binding.btnAnonymous.hide()
|
||||
}
|
||||
|
||||
override fun resetActions() {
|
||||
super.resetActions()
|
||||
|
||||
binding.btnAnonymous.hide()
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package com.aurora.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.UrlQuerySanitizer
|
||||
import android.os.Bundle
|
||||
|
||||
inline fun <reified T : Context> Context.newIntent(): Intent =
|
||||
@@ -40,3 +41,22 @@ inline fun <reified T : Context> Context.newIntent(flags: Int, extras: Bundle):
|
||||
intent.putExtras(extras)
|
||||
return intent
|
||||
}
|
||||
|
||||
fun Intent.getPackageName(fallbackBundle: Bundle? = null): String? {
|
||||
return when (action) {
|
||||
Intent.ACTION_VIEW -> {
|
||||
data?.getQueryParameter("id")
|
||||
}
|
||||
Intent.ACTION_SEND -> {
|
||||
val clipData = getStringExtra(Intent.EXTRA_TEXT).orEmpty()
|
||||
UrlQuerySanitizer(clipData).getValue("id")
|
||||
}
|
||||
Intent.ACTION_SHOW_APP_INFO -> {
|
||||
extras?.getString(Intent.EXTRA_PACKAGE_NAME)
|
||||
}
|
||||
else -> {
|
||||
extras?.getString("packageName") ?: fallbackBundle?.getString("packageName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.CompositeDecoder
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.encoding.decodeStructure
|
||||
import kotlinx.serialization.encoding.encodeStructure
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Serializer for [Locale] for working with Kotlin's Serialization library
|
||||
*/
|
||||
object LocaleSerializer : KSerializer<Locale> {
|
||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Locale") {
|
||||
element<String>("language")
|
||||
element<String>("region")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Locale) {
|
||||
return encoder.encodeStructure(descriptor) {
|
||||
encodeStringElement(descriptor, 0, value.language)
|
||||
encodeStringElement(descriptor, 1, value.country)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Locale {
|
||||
return decoder.decodeStructure(descriptor) {
|
||||
var language: String? = null
|
||||
var region: String? = null
|
||||
|
||||
while (true) {
|
||||
when (val index = decodeElementIndex(descriptor)) {
|
||||
0 -> language = decodeStringElement(descriptor, 0)
|
||||
1 -> region = decodeStringElement(descriptor, 1)
|
||||
CompositeDecoder.DECODE_DONE -> break
|
||||
else -> error("Unexpected index: $index")
|
||||
}
|
||||
}
|
||||
|
||||
require(!language.isNullOrBlank() && !region.isNullOrBlank())
|
||||
Locale.Builder()
|
||||
.setLanguage(language)
|
||||
.setRegion(region)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.Properties
|
||||
|
||||
/**
|
||||
* Serializer for [Properties] for working with Kotlin's Serialization library
|
||||
*/
|
||||
object PropertiesSerializer : KSerializer<Properties> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
MapSerializer(String.serializer(), String.serializer()).descriptor
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Properties) {
|
||||
val map = value.stringPropertyNames().associateWith { value.getProperty(it) }
|
||||
MapSerializer(String.serializer(), String.serializer()).serialize(encoder, map)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Properties {
|
||||
val map = MapSerializer(String.serializer(), String.serializer()).deserialize(decoder)
|
||||
return Properties().apply { putAll(map) }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.aurora.store.module
|
||||
|
||||
import com.aurora.store.data.serializers.LocaleSerializer
|
||||
import com.aurora.store.data.serializers.PropertiesSerializer
|
||||
import com.aurora.gplayapi.data.serializers.LocaleSerializer
|
||||
import com.aurora.gplayapi.data.serializers.PropertiesSerializer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.aurora.store.BuildConfig
|
||||
|
||||
object Preferences {
|
||||
|
||||
@@ -65,10 +66,17 @@ object Preferences {
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences {
|
||||
if (prefs == null) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return when (BuildConfig.FLAVOR) {
|
||||
"vanilla" -> {
|
||||
prefs ?: PreferenceManager.getDefaultSharedPreferences(context).also { prefs = it }
|
||||
}
|
||||
|
||||
else -> {
|
||||
val prefName = "${context.packageName}_${BuildConfig.FLAVOR}_preferences"
|
||||
prefs ?: context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
|
||||
.also { prefs = it }
|
||||
}
|
||||
}
|
||||
return prefs!!
|
||||
}
|
||||
|
||||
fun remove(context: Context, key: String) {
|
||||
|
||||
@@ -21,13 +21,14 @@ package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewInfoBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseModel
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import java.util.Locale
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
@@ -41,16 +42,14 @@ class InfoView @JvmOverloads constructor(
|
||||
|
||||
@ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode])
|
||||
fun badge(info: Map.Entry<String, String>) {
|
||||
binding.txtTitle.text = info.key
|
||||
.replace("_", " ")
|
||||
.lowercase(Locale.getDefault())
|
||||
.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(Locale.getDefault())
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
binding.txtTitle.text = when (info.key) {
|
||||
"DOWNLOAD" -> ContextCompat.getString(context, R.string.app_info_downloads)
|
||||
"UPDATED_ON" -> ContextCompat.getString(context, R.string.app_info_updated_on)
|
||||
"REQUIRES" -> ContextCompat.getString(context, R.string.app_info_min_android)
|
||||
"TARGET" -> ContextCompat.getString(context, R.string.app_info_target_android)
|
||||
else -> info.key
|
||||
}
|
||||
|
||||
binding.txtSubtitle.text = info.value
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
|
||||
|
||||
lateinit var permissionProvider: PermissionProvider
|
||||
|
||||
private var _binding: ViewBindingType? = null
|
||||
protected open var _binding: ViewBindingType? = null
|
||||
protected val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -58,9 +58,9 @@ class StreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.initCluster(streamCluster)
|
||||
viewModel.seedCluster(streamCluster)
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(streamCluster)
|
||||
updateController(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ class DetailsMoreFragment : BaseFragment<FragmentDetailsMoreBinding>() {
|
||||
add(
|
||||
InfoViewModel_()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.badge(mapOf("targets" to "API ${app.targetSdk}").entries.first())
|
||||
.badge(mapOf("TARGET" to "${app.targetSdk}").entries.first())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.aurora.store.view.ui.onboarding
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.aurora.store.databinding.FragmentAppLinksBinding
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppLinksFragment : BaseFragment<FragmentAppLinksBinding>() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,9 @@
|
||||
/*
|
||||
* 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.onboarding
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
@@ -29,59 +12,36 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.areNotificationsEnabled
|
||||
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.UpdateMode
|
||||
import com.aurora.store.data.work.CacheWorker
|
||||
import com.aurora.store.databinding.FragmentOnboardingBinding
|
||||
import com.aurora.store.util.CertUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_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.Preferences.PREFERENCE_VENDING_VERSION
|
||||
import com.aurora.store.util.save
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.onboarding.OnboardingViewModel
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
abstract class BaseFlavouredOnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
|
||||
private val viewModel: OnboardingViewModel by viewModels()
|
||||
val viewModel: OnboardingViewModel by viewModels()
|
||||
|
||||
private var lastPosition = 0
|
||||
var lastPosition = 0
|
||||
|
||||
internal class PagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
when (position) {
|
||||
0 -> return WelcomeFragment()
|
||||
1 -> return PermissionsFragment.newInstance()
|
||||
2 -> return AppLinksFragment()
|
||||
}
|
||||
return Fragment()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return 2
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentOnboardingBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -95,6 +55,7 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
}
|
||||
|
||||
val isDefaultPrefLoaded = Preferences.getBoolean(requireContext(), PREFERENCE_DEFAULT)
|
||||
|
||||
if (!isDefaultPrefLoaded) {
|
||||
save(PREFERENCE_DEFAULT, true)
|
||||
loadDefaultPreferences()
|
||||
@@ -105,7 +66,11 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
|
||||
// ViewPager2
|
||||
binding.viewpager2.apply {
|
||||
adapter = PagerAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)
|
||||
adapter = PagerAdapter(
|
||||
childFragmentManager,
|
||||
viewLifecycleOwner.lifecycle,
|
||||
onboardingPages()
|
||||
)
|
||||
isUserInputEnabled = false
|
||||
setCurrentItem(0, true)
|
||||
registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||
@@ -149,7 +114,11 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishOnboarding() {
|
||||
abstract fun loadDefaultPreferences()
|
||||
|
||||
abstract fun onboardingPages(): List<Fragment>
|
||||
|
||||
open fun finishOnboarding() {
|
||||
setupAutoUpdates()
|
||||
CacheWorker.scheduleAutomatedCacheCleanup(requireContext())
|
||||
Preferences.putBooleanNow(requireContext(), PREFERENCE_INTRO, true)
|
||||
@@ -158,40 +127,29 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
|
||||
ProcessPhoenix.triggerRebirth(context)
|
||||
}
|
||||
|
||||
private fun loadDefaultPreferences() {
|
||||
/*Filters*/
|
||||
save(PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
save(PREFERENCE_FILTER_FDROID, true)
|
||||
|
||||
/*Network*/
|
||||
// TODO: Gather feedback and drop setting default dispenser for all builds
|
||||
if (!CertUtil.isAppGalleryApp(requireContext(), requireContext().packageName)) {
|
||||
save(PREFERENCE_DISPENSER_URLS, setOf(Constants.URL_DISPENSER))
|
||||
}
|
||||
save(PREFERENCE_VENDING_VERSION, 0)
|
||||
|
||||
/*Customization*/
|
||||
save(PREFERENCE_THEME_STYLE, 0)
|
||||
save(PREFERENCE_DEFAULT_SELECTED_TAB, 0)
|
||||
save(PREFERENCE_FOR_YOU, true)
|
||||
save(PREFERENCE_SIMILAR, false)
|
||||
|
||||
/*Installer*/
|
||||
save(PREFERENCE_AUTO_DELETE, true)
|
||||
save(PREFERENCE_INSTALLER_ID, 0)
|
||||
|
||||
/*Updates*/
|
||||
save(PREFERENCE_UPDATES_EXTENDED, false)
|
||||
save(PREFERENCE_UPDATES_CHECK_INTERVAL, 3)
|
||||
}
|
||||
|
||||
private fun setupAutoUpdates() {
|
||||
open fun setupAutoUpdates() {
|
||||
val updateMode = when {
|
||||
requireContext().isIgnoringBatteryOptimizations() -> UpdateMode.CHECK_AND_INSTALL
|
||||
requireContext().areNotificationsEnabled() -> UpdateMode.CHECK_AND_NOTIFY
|
||||
else -> UpdateMode.DISABLED
|
||||
}
|
||||
|
||||
save(PREFERENCE_UPDATES_AUTO, updateMode.ordinal)
|
||||
|
||||
viewModel.updateHelper.scheduleAutomatedCheck()
|
||||
}
|
||||
|
||||
internal class PagerAdapter(
|
||||
fragmentManager: FragmentManager,
|
||||
lifecycle: Lifecycle,
|
||||
var items: List<Fragment>
|
||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return items[position]
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,24 @@
|
||||
/*
|
||||
* 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.splash
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Intent
|
||||
import android.net.UrlQuerySanitizer
|
||||
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.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.getPackageName
|
||||
import com.aurora.extensions.isMAndAbove
|
||||
import com.aurora.extensions.isNAndAbove
|
||||
import com.aurora.extensions.navigate
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
@@ -56,22 +35,20 @@ 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 dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
abstract class BaseFlavouredSplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
|
||||
private val TAG = SplashFragment::class.java.simpleName
|
||||
private val TAG = BaseFlavouredSplashFragment::class.java.simpleName
|
||||
|
||||
private val viewModel: AuthViewModel by activityViewModels()
|
||||
val viewModel: AuthViewModel by activityViewModels()
|
||||
|
||||
private val canLoginWithMicroG: Boolean
|
||||
val canLoginWithMicroG: Boolean
|
||||
get() = isMAndAbove && PackageUtil.hasSupportedMicroG(requireContext()) &&
|
||||
Preferences.getBoolean(requireContext(), PREFERENCE_MICROG_AUTH, true)
|
||||
|
||||
private val startForAccount =
|
||||
val startForAccount =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val accountName = it.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
|
||||
if (!accountName.isNullOrBlank()) {
|
||||
@@ -81,6 +58,15 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentSplashBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -115,9 +101,6 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
|
||||
attachActions()
|
||||
|
||||
// Show anonymous logins if we have dispenser URL
|
||||
binding.btnAnonymous.isVisible = !viewModel.authProvider.dispenserURL.isNullOrBlank()
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.authState.collectLatest {
|
||||
when (it) {
|
||||
@@ -128,7 +111,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
AuthState.Valid -> {
|
||||
val packageName = getPackageName(requireActivity().intent)
|
||||
val packageName =
|
||||
requireActivity().intent.getPackageName(requireArguments())
|
||||
if (packageName.isNullOrBlank()) {
|
||||
navigateToDefaultTab()
|
||||
} else {
|
||||
@@ -152,7 +136,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
AuthState.SignedIn -> {
|
||||
val packageName = getPackageName(requireActivity().intent)
|
||||
val packageName =
|
||||
requireActivity().intent.getPackageName(requireArguments())
|
||||
if (packageName.isNullOrBlank()) {
|
||||
navigateToDefaultTab()
|
||||
} else {
|
||||
@@ -193,55 +178,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
private fun updateActionLayout(isVisible: Boolean) {
|
||||
if (isVisible) {
|
||||
binding.layoutAction.show()
|
||||
binding.toolbar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.layoutAction.hide()
|
||||
binding.toolbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetActions() {
|
||||
binding.btnGoogle.apply {
|
||||
updateProgress(false)
|
||||
isEnabled = true
|
||||
}
|
||||
|
||||
binding.btnAnonymous.apply {
|
||||
updateProgress(false)
|
||||
isEnabled = true
|
||||
}
|
||||
binding.layoutAction.isVisible = isVisible
|
||||
binding.toolbar.isVisible = isVisible
|
||||
}
|
||||
|
||||
private fun navigateToDefaultTab() {
|
||||
@@ -262,29 +200,6 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
findNavController().navigate(directions)
|
||||
}
|
||||
|
||||
private fun getPackageName(intent: Intent): String? {
|
||||
return when {
|
||||
intent.action == Intent.ACTION_VIEW -> {
|
||||
intent.data!!.getQueryParameter("id")
|
||||
}
|
||||
|
||||
intent.action == Intent.ACTION_SEND -> {
|
||||
val clipData = intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
|
||||
UrlQuerySanitizer(clipData).getValue("id")
|
||||
}
|
||||
|
||||
isNAndAbove && intent.action == Intent.ACTION_SHOW_APP_INFO -> {
|
||||
intent.extras?.getString(Intent.EXTRA_PACKAGE_NAME)
|
||||
}
|
||||
|
||||
intent.extras != null -> {
|
||||
intent.extras?.getString("packageName")
|
||||
}
|
||||
|
||||
else -> requireArguments().getString("packageName")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestAuthTokenForGoogle(accountName: String, oldToken: String? = null) {
|
||||
try {
|
||||
if (oldToken != null) {
|
||||
@@ -317,4 +232,46 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -42,32 +41,36 @@ class StreamBrowseViewModel @Inject constructor(
|
||||
|
||||
private lateinit var streamCluster: StreamCluster
|
||||
|
||||
fun initCluster(cluster: StreamCluster) {
|
||||
fun seedCluster(cluster: StreamCluster) {
|
||||
streamCluster = cluster
|
||||
liveData.postValue(streamCluster)
|
||||
}
|
||||
|
||||
fun nextCluster() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val nextCluster = streamHelper.nextStreamCluster(
|
||||
streamCluster.clusterNextPageUrl
|
||||
)
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val next = streamHelper.nextStreamCluster(streamCluster.clusterNextPageUrl)
|
||||
|
||||
streamCluster = streamCluster.copy(
|
||||
clusterNextPageUrl = nextCluster.clusterNextPageUrl,
|
||||
clusterAppList = streamCluster.clusterAppList + nextCluster.clusterAppList
|
||||
)
|
||||
streamCluster = streamCluster.copy(
|
||||
clusterNextPageUrl = next.clusterNextPageUrl,
|
||||
clusterAppList = streamCluster.clusterAppList + next.clusterAppList
|
||||
)
|
||||
|
||||
liveData.postValue(streamCluster)
|
||||
} else {
|
||||
Log.i(TAG, "End of Cluster")
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
liveData.postValue(streamCluster)
|
||||
} else {
|
||||
Log.i(TAG, "End of Cluster")
|
||||
postClusterEnd()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun postClusterEnd() {
|
||||
streamCluster = streamCluster.copy(
|
||||
clusterNextPageUrl = ""
|
||||
)
|
||||
liveData.postValue(streamCluster)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,9 @@ class DetailsClusterViewModel @Inject constructor(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
||||
it.remove(clusterID)
|
||||
it[clusterID] = mergedCluster
|
||||
|
||||
val newStreamClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = mergedCluster
|
||||
}
|
||||
|
||||
stash.put(url, bundle.copy(streamClusters = newStreamClusters))
|
||||
|
||||
@@ -90,9 +90,9 @@ class DevProfileViewModel @Inject constructor(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
val newStreamClusters = streamBundle.streamClusters.toMutableMap().also {
|
||||
it.remove(newCluster.id)
|
||||
it[newCluster.id] = mergedCluster
|
||||
|
||||
val newStreamClusters = streamBundle.streamClusters.toMutableMap().apply {
|
||||
this[newCluster.id] = mergedCluster
|
||||
}
|
||||
|
||||
streamBundle = streamBundle.copy(streamClusters = newStreamClusters)
|
||||
|
||||
@@ -32,7 +32,8 @@ import com.aurora.store.data.model.ViewState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -44,11 +45,14 @@ class StreamViewModel @Inject constructor(
|
||||
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
|
||||
private var stash: HomeStash = mutableMapOf()
|
||||
private val stash: HomeStash = mutableMapOf()
|
||||
|
||||
private val streamContract: StreamContract
|
||||
get() = webStreamHelper
|
||||
|
||||
// Mutex to protect stash access for thread safety
|
||||
private val stashMutex = Mutex()
|
||||
|
||||
fun getStreamBundle(category: StreamContract.Category, type: StreamContract.Type) {
|
||||
liveData.postValue(ViewState.Loading)
|
||||
observe(category, type)
|
||||
@@ -56,16 +60,18 @@ class StreamViewModel @Inject constructor(
|
||||
|
||||
fun observe(category: StreamContract.Category, type: StreamContract.Type) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
val bundle = targetBundle(category)
|
||||
if (bundle.hasCluster()) {
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
}
|
||||
try {
|
||||
stashMutex.withLock {
|
||||
val bundle = targetBundle(category)
|
||||
|
||||
// Post existing data if any clusters exist
|
||||
if (bundle.hasCluster()) {
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
}
|
||||
|
||||
try {
|
||||
if (!bundle.hasCluster() || bundle.hasNext()) {
|
||||
|
||||
//Fetch new stream bundle
|
||||
// Fetch new stream bundle
|
||||
val newBundle = if (bundle.hasCluster()) {
|
||||
streamContract.nextStreamBundle(
|
||||
category,
|
||||
@@ -75,41 +81,47 @@ class StreamViewModel @Inject constructor(
|
||||
streamContract.fetch(type, category)
|
||||
}
|
||||
|
||||
//Update old bundle
|
||||
// Update old bundle
|
||||
val mergedBundle = bundle.copy(
|
||||
streamClusters = bundle.streamClusters + newBundle.streamClusters,
|
||||
streamNextPageUrl = newBundle.streamNextPageUrl
|
||||
)
|
||||
stash[category] = mergedBundle
|
||||
|
||||
//Post updated to UI
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
// Post updated to UI
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
} else {
|
||||
Log.i(TAG, "End of Bundle")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun observeCluster(category: StreamContract.Category, streamCluster: StreamCluster) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster = streamContract.nextStreamCluster(
|
||||
streamCluster.clusterNextPageUrl
|
||||
)
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster = streamContract.nextStreamCluster(
|
||||
streamCluster.clusterNextPageUrl
|
||||
)
|
||||
|
||||
stashMutex.withLock {
|
||||
updateCluster(category, streamCluster.id, newCluster)
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
} else {
|
||||
Log.i(TAG, "End of cluster")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
} else {
|
||||
stashMutex.withLock {
|
||||
postClusterEnd(category, streamCluster.id)
|
||||
}
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,26 +131,34 @@ class StreamViewModel @Inject constructor(
|
||||
clusterID: Int,
|
||||
newCluster: StreamCluster
|
||||
) {
|
||||
val bundle = targetBundle(category)
|
||||
bundle.streamClusters[clusterID]?.let { oldCluster ->
|
||||
val mergedCluster = oldCluster.copy(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
||||
it.remove(clusterID)
|
||||
it[clusterID] = mergedCluster
|
||||
}
|
||||
val bundle = stash[category] ?: return
|
||||
val oldCluster = bundle.streamClusters[clusterID] ?: return
|
||||
|
||||
stash.put(category, bundle.copy(streamClusters = newStreamClusters))
|
||||
val mergedCluster = oldCluster.copy(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
|
||||
val updatedClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = mergedCluster
|
||||
}
|
||||
|
||||
stash[category] = bundle.copy(streamClusters = updatedClusters)
|
||||
}
|
||||
|
||||
private fun postClusterEnd(category: StreamContract.Category, clusterID: Int) {
|
||||
val bundle = stash[category] ?: return
|
||||
val oldCluster = bundle.streamClusters[clusterID] ?: return
|
||||
|
||||
val updatedCluster = oldCluster.copy(clusterNextPageUrl = "")
|
||||
val updatedClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = updatedCluster
|
||||
}
|
||||
|
||||
stash[category] = bundle.copy(streamClusters = updatedClusters)
|
||||
}
|
||||
|
||||
private fun targetBundle(category: StreamContract.Category): StreamBundle {
|
||||
val streamBundle = stash.getOrPut(category) {
|
||||
StreamBundle()
|
||||
}
|
||||
|
||||
return streamBundle
|
||||
return stash.getOrPut(category) { StreamBundle() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package com.aurora.store.viewmodel.review
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -28,13 +29,13 @@ import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ReviewViewModel @Inject constructor(
|
||||
private val reviewsHelper: ReviewsHelper
|
||||
) : ViewModel() {
|
||||
val TAG = javaClass.simpleName
|
||||
|
||||
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
|
||||
|
||||
@@ -42,29 +43,29 @@ class ReviewViewModel @Inject constructor(
|
||||
|
||||
fun fetchReview(packageName: String, filter: Review.Filter) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
try {
|
||||
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to fetch reviews", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun next(nextReviewPageUrl: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
val nextReviewCluster = reviewsHelper.next(nextReviewPageUrl)
|
||||
reviewsCluster = reviewsCluster.copy(
|
||||
nextPageUrl = nextReviewCluster.nextPageUrl,
|
||||
reviewList = nextReviewCluster.reviewList + nextReviewCluster.reviewList
|
||||
)
|
||||
try {
|
||||
val currentCluster = reviewsCluster
|
||||
val nextReviewCluster = reviewsHelper.next(nextReviewPageUrl)
|
||||
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
reviewsCluster = currentCluster.copy(
|
||||
nextPageUrl = nextReviewCluster.nextPageUrl,
|
||||
reviewList = currentCluster.reviewList + nextReviewCluster.reviewList
|
||||
)
|
||||
|
||||
liveData.postValue(reviewsCluster)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to fetch next reviews $nextReviewPageUrl", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@ class CategoryStreamViewModel @Inject constructor(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
||||
it.remove(clusterID)
|
||||
it[clusterID] = mergedCluster
|
||||
|
||||
val newStreamClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = mergedCluster
|
||||
}
|
||||
|
||||
stash.put(browseUrl, bundle.copy(streamClusters = newStreamClusters))
|
||||
|
||||
@@ -91,7 +91,7 @@ class TopChartViewModel @Inject constructor(
|
||||
clusterAppList = streamCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
|
||||
stash[type] = mutableMapOf(chart to mergedCluster)
|
||||
stash[type]?.set(chart, mergedCluster)
|
||||
}
|
||||
|
||||
private fun targetCluster(
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.onboarding.AppLinksFragment">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/appCompatTextView2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="true"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="@string/app_links_title"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="42sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/appCompatTextView"
|
||||
style="@style/AuroraTextStyle.Subtitle.Alt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="@dimen/margin_small"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="@string/app_links_desc"
|
||||
android:textAlignment="textStart"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appCompatTextView2" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxyRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"
|
||||
android:padding="@dimen/padding_medium"
|
||||
tools:listitem="@layout/view_permission_switch" />
|
||||
</LinearLayout>
|
||||
@@ -357,6 +357,10 @@
|
||||
<string name="android_market_desc">Android Market was an online store offering software applications designed for Android devices, retired in 2017.</string>
|
||||
<string name="failed_apk_export">Failed to export APKs</string>
|
||||
<string name="no_apps_available">No apps available</string>
|
||||
<string name="app_info_downloads">"Downloads"</string>
|
||||
<string name="app_info_updated_on">"Last updated"</string>
|
||||
<string name="app_info_min_android">"Minimum Android Version"</string>
|
||||
<string name="app_info_target_android">"Target API Level"</string>
|
||||
|
||||
<!-- InstallerFragment -->
|
||||
<string name="session_installer_subtitle">Session based installer for bundled/split APKs</string>
|
||||
|
||||
7
app/src/preload/AndroidManifest.xml
Normal file
7
app/src/preload/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.onboarding
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.aurora.Constants
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
|
||||
import com.aurora.store.util.save
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
|
||||
override fun loadDefaultPreferences() {
|
||||
/*Filters*/
|
||||
save(PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
save(PREFERENCE_FILTER_FDROID, true)
|
||||
|
||||
/*Network*/
|
||||
save(PREFERENCE_DISPENSER_URLS, setOf(Constants.URL_DISPENSER))
|
||||
save(PREFERENCE_VENDING_VERSION, 0)
|
||||
|
||||
/*Customization*/
|
||||
save(PREFERENCE_THEME_STYLE, 0)
|
||||
save(PREFERENCE_DEFAULT_SELECTED_TAB, 0)
|
||||
save(PREFERENCE_FOR_YOU, true)
|
||||
save(PREFERENCE_SIMILAR, false)
|
||||
|
||||
/*Installer*/
|
||||
save(PREFERENCE_AUTO_DELETE, true)
|
||||
save(PREFERENCE_INSTALLER_ID, 0)
|
||||
|
||||
/*Updates*/
|
||||
save(PREFERENCE_UPDATES_EXTENDED, false)
|
||||
save(PREFERENCE_UPDATES_CHECK_INTERVAL, 3)
|
||||
}
|
||||
|
||||
override fun onboardingPages(): List<Fragment> {
|
||||
return listOf(
|
||||
WelcomeFragment(),
|
||||
PermissionsFragment.newInstance()
|
||||
)
|
||||
}
|
||||
|
||||
override fun setupAutoUpdates() {
|
||||
super.setupAutoUpdates()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
|
||||
override fun finishOnboarding() {
|
||||
super.finishOnboarding()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.splash
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SplashFragment : BaseFlavouredSplashFragment()
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.onboarding
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.aurora.Constants
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
|
||||
import com.aurora.store.util.save
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : BaseFlavouredOnboardingFragment() {
|
||||
|
||||
override fun loadDefaultPreferences() {
|
||||
/*Filters*/
|
||||
save(PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
save(PREFERENCE_FILTER_FDROID, true)
|
||||
|
||||
/*Network*/
|
||||
save(PREFERENCE_DISPENSER_URLS, setOf(Constants.URL_DISPENSER))
|
||||
save(PREFERENCE_VENDING_VERSION, 0)
|
||||
|
||||
/*Customization*/
|
||||
save(PREFERENCE_THEME_STYLE, 0)
|
||||
save(PREFERENCE_DEFAULT_SELECTED_TAB, 0)
|
||||
save(PREFERENCE_FOR_YOU, true)
|
||||
save(PREFERENCE_SIMILAR, false)
|
||||
|
||||
/*Installer*/
|
||||
save(PREFERENCE_AUTO_DELETE, true)
|
||||
save(PREFERENCE_INSTALLER_ID, 0)
|
||||
|
||||
/*Updates*/
|
||||
save(PREFERENCE_UPDATES_EXTENDED, false)
|
||||
save(PREFERENCE_UPDATES_CHECK_INTERVAL, 3)
|
||||
}
|
||||
|
||||
override fun onboardingPages(): List<Fragment> {
|
||||
return listOf(
|
||||
WelcomeFragment(),
|
||||
PermissionsFragment.newInstance()
|
||||
)
|
||||
}
|
||||
|
||||
override fun setupAutoUpdates() {
|
||||
super.setupAutoUpdates()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
|
||||
override fun finishOnboarding() {
|
||||
super.finishOnboarding()
|
||||
|
||||
// Remove super & implement variant logic here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.splash
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SplashFragment : BaseFlavouredSplashFragment()
|
||||
@@ -9,7 +9,7 @@ composeBom = "2025.05.01"
|
||||
core = "1.16.0"
|
||||
epoxy = "5.1.4"
|
||||
espresso = "3.6.1"
|
||||
gplayapi = "3.5.2"
|
||||
gplayapi = "3.5.3"
|
||||
hiddenapibypass = "6.1"
|
||||
hilt = "2.56.2"
|
||||
hiltWork = "1.2.0"
|
||||
|
||||
Reference in New Issue
Block a user