diff --git a/.gitignore b/.gitignore
index 6452a1e7a..1687bfbd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a052bffdd..710c2bc41 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
}
diff --git a/app/lint.xml b/app/lint.xml
index 171a4f24a..b4a690a2b 100644
--- a/app/lint.xml
+++ b/app/lint.xml
@@ -1,6 +1,7 @@
+
diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml
new file mode 100644
index 000000000..ea1a0693d
--- /dev/null
+++ b/app/src/huawei/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/huawei/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/huawei/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
new file mode 100644
index 000000000..d2119f027
--- /dev/null
+++ b/app/src/huawei/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
@@ -0,0 +1,83 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.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 {
+ 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
+ }
+}
diff --git a/app/src/huawei/java/com/aurora/store/view/ui/splash/SplashFragment.kt b/app/src/huawei/java/com/aurora/store/view/ui/splash/SplashFragment.kt
new file mode 100644
index 000000000..2b5f789be
--- /dev/null
+++ b/app/src/huawei/java/com/aurora/store/view/ui/splash/SplashFragment.kt
@@ -0,0 +1,38 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.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()
+ }
+}
diff --git a/app/src/main/java/com/aurora/extensions/Intent.kt b/app/src/main/java/com/aurora/extensions/Intent.kt
index c270615ab..e9b159401 100644
--- a/app/src/main/java/com/aurora/extensions/Intent.kt
+++ b/app/src/main/java/com/aurora/extensions/Intent.kt
@@ -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 Context.newIntent(): Intent =
@@ -40,3 +41,22 @@ inline fun 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")
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/aurora/store/data/serializers/LocaleSerializer.kt b/app/src/main/java/com/aurora/store/data/serializers/LocaleSerializer.kt
deleted file mode 100644
index e05f7c1bf..000000000
--- a/app/src/main/java/com/aurora/store/data/serializers/LocaleSerializer.kt
+++ /dev/null
@@ -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 {
- override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Locale") {
- element("language")
- element("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()
- }
- }
-}
diff --git a/app/src/main/java/com/aurora/store/data/serializers/PropertiesSerializer.kt b/app/src/main/java/com/aurora/store/data/serializers/PropertiesSerializer.kt
deleted file mode 100644
index c92906e2c..000000000
--- a/app/src/main/java/com/aurora/store/data/serializers/PropertiesSerializer.kt
+++ /dev/null
@@ -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 {
- 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) }
- }
-}
diff --git a/app/src/main/java/com/aurora/store/module/CommonModule.kt b/app/src/main/java/com/aurora/store/module/CommonModule.kt
index 34730000c..ede459367 100644
--- a/app/src/main/java/com/aurora/store/module/CommonModule.kt
+++ b/app/src/main/java/com/aurora/store/module/CommonModule.kt
@@ -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
diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt
index 393de4c2c..326dd27a2 100644
--- a/app/src/main/java/com/aurora/store/util/Preferences.kt
+++ b/app/src/main/java/com/aurora/store/util/Preferences.kt
@@ -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) {
diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/InfoView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/InfoView.kt
index 13fa11e2d..71ba132e0 100644
--- a/app/src/main/java/com/aurora/store/view/epoxy/views/details/InfoView.kt
+++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/InfoView.kt
@@ -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) {
- 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
}
diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt
index 75725406f..08bb684d1 100644
--- a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt
@@ -42,7 +42,7 @@ abstract class BaseFragment : 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?) {
diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseFragment.kt
index 2c0f0c8f6..816add47f 100644
--- a/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseFragment.kt
@@ -58,9 +58,9 @@ class StreamBrowseFragment : BaseFragment() {
}
})
- viewModel.initCluster(streamCluster)
+ viewModel.seedCluster(streamCluster)
viewModel.liveData.observe(viewLifecycleOwner) {
- updateController(streamCluster)
+ updateController(it)
}
}
diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreFragment.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreFragment.kt
index b59d350e6..0651214c7 100644
--- a/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreFragment.kt
@@ -164,7 +164,7 @@ class DetailsMoreFragment : BaseFragment() {
add(
InfoViewModel_()
.id(UUID.randomUUID().toString())
- .badge(mapOf("targets" to "API ${app.targetSdk}").entries.first())
+ .badge(mapOf("TARGET" to "${app.targetSdk}").entries.first())
)
}
}
diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt
deleted file mode 100644
index 13bbc6cf8..000000000
--- a/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt
+++ /dev/null
@@ -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() {
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- }
-}
diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/BaseFlavouredOnboardingFragment.kt
similarity index 57%
rename from app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
rename to app/src/main/java/com/aurora/store/view/ui/onboarding/BaseFlavouredOnboardingFragment.kt
index db797ba60..00a7b6f4a 100644
--- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/BaseFlavouredOnboardingFragment.kt
@@ -1,26 +1,9 @@
-/*
- * Aurora Store
- * Copyright (C) 2021, Rahul Kumar Patel
- *
- * Aurora Store is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * Aurora Store is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Aurora Store. If not, see .
- *
- */
-
package com.aurora.store.view.ui.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() {
+abstract class BaseFlavouredOnboardingFragment : BaseFragment() {
- 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() {
}
val isDefaultPrefLoaded = Preferences.getBoolean(requireContext(), PREFERENCE_DEFAULT)
+
if (!isDefaultPrefLoaded) {
save(PREFERENCE_DEFAULT, true)
loadDefaultPreferences()
@@ -105,7 +66,11 @@ class OnboardingFragment : BaseFragment() {
// 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() {
}
}
- private fun finishOnboarding() {
+ abstract fun loadDefaultPreferences()
+
+ abstract fun onboardingPages(): List
+
+ open fun finishOnboarding() {
setupAutoUpdates()
CacheWorker.scheduleAutomatedCacheCleanup(requireContext())
Preferences.putBooleanNow(requireContext(), PREFERENCE_INTRO, true)
@@ -158,40 +127,29 @@ class OnboardingFragment : BaseFragment() {
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
+ ) : FragmentStateAdapter(fragmentManager, lifecycle) {
+ override fun createFragment(position: Int): Fragment {
+ return items[position]
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+ }
}
diff --git a/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt b/app/src/main/java/com/aurora/store/view/ui/splash/BaseFlavouredSplashFragment.kt
similarity index 78%
rename from app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt
rename to app/src/main/java/com/aurora/store/view/ui/splash/BaseFlavouredSplashFragment.kt
index d69cbe170..a8adb7ad0 100644
--- a/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/splash/BaseFlavouredSplashFragment.kt
@@ -1,45 +1,24 @@
-/*
- * Aurora Store
- * Copyright (C) 2021, Rahul Kumar Patel
- *
- * Aurora Store is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * Aurora Store is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Aurora Store. If not, see .
- *
- */
-
package com.aurora.store.view.ui.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() {
+abstract class BaseFlavouredSplashFragment : BaseFragment() {
- 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() {
}
}
+ 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() {
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() {
}
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() {
}
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() {
}
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() {
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() {
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
+ }
+ }
}
diff --git a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt
index 2915d638a..a5237728e 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt
@@ -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)
+ }
}
diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/DetailsClusterViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsClusterViewModel.kt
index a069e1aab..d2a14e28d 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/details/DetailsClusterViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsClusterViewModel.kt
@@ -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))
diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/DevProfileViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/DevProfileViewModel.kt
index 28da92cea..2825fb8c7 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/details/DevProfileViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/details/DevProfileViewModel.kt
@@ -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)
diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt
index 4e28d0676..64201ea9e 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt
@@ -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 = 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() }
}
}
diff --git a/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt
index 9cc4a7d5c..140f4a402 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt
@@ -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 = 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)
}
}
}
diff --git a/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt
index cc1af5ca9..4c80088c4 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt
@@ -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))
diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt
index 4fc52da0d..2f0afecb0 100644
--- a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt
+++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt
@@ -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(
diff --git a/app/src/main/res/layout/fragment_app_links.xml b/app/src/main/res/layout/fragment_app_links.xml
deleted file mode 100644
index 6d8e1e1e3..000000000
--- a/app/src/main/res/layout/fragment_app_links.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f0a7e1d6d..54747d80b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -357,6 +357,10 @@
Android Market was an online store offering software applications designed for Android devices, retired in 2017.
Failed to export APKs
No apps available
+ "Downloads"
+ "Last updated"
+ "Minimum Android Version"
+ "Target API Level"
Session based installer for bundled/split APKs
diff --git a/app/src/preload/AndroidManifest.xml b/app/src/preload/AndroidManifest.xml
new file mode 100644
index 000000000..ea1a0693d
--- /dev/null
+++ b/app/src/preload/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/preload/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/preload/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
new file mode 100644
index 000000000..cadc94c05
--- /dev/null
+++ b/app/src/preload/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
@@ -0,0 +1,84 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.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 {
+ 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
+ }
+}
diff --git a/app/src/preload/java/com/aurora/store/view/ui/splash/SplashFragment.kt b/app/src/preload/java/com/aurora/store/view/ui/splash/SplashFragment.kt
new file mode 100644
index 000000000..d965d10e4
--- /dev/null
+++ b/app/src/preload/java/com/aurora/store/view/ui/splash/SplashFragment.kt
@@ -0,0 +1,25 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.splash
+
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SplashFragment : BaseFlavouredSplashFragment()
diff --git a/app/src/vanilla/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/vanilla/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
new file mode 100644
index 000000000..cadc94c05
--- /dev/null
+++ b/app/src/vanilla/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt
@@ -0,0 +1,84 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.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 {
+ 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
+ }
+}
diff --git a/app/src/vanilla/java/com/aurora/store/view/ui/splash/SplashFragment.kt b/app/src/vanilla/java/com/aurora/store/view/ui/splash/SplashFragment.kt
new file mode 100644
index 000000000..d965d10e4
--- /dev/null
+++ b/app/src/vanilla/java/com/aurora/store/view/ui/splash/SplashFragment.kt
@@ -0,0 +1,25 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.view.ui.splash
+
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SplashFragment : BaseFlavouredSplashFragment()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 968be725a..5c0b88328 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -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"