diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/RetrofitModule.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/RetrofitModule.kt
index 911f4affafafcb9dd4068df2a555a539738f4f5a..b03baa0b3916ed7aafc3bed8819b1403633fbd8a 100644
--- a/app/src/main/java/foundation/e/apps/api/cleanapk/RetrofitModule.kt
+++ b/app/src/main/java/foundation/e/apps/api/cleanapk/RetrofitModule.kt
@@ -34,6 +34,7 @@ import foundation.e.apps.api.cleanapk.data.app.Application
import foundation.e.apps.api.ecloud.EcloudApiInterface
import foundation.e.apps.api.exodus.ExodusTrackerApi
import foundation.e.apps.api.fdroid.FdroidApiInterface
+import foundation.e.apps.api.fdroid.FdroidWebInterface
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -117,6 +118,18 @@ object RetrofitModule {
.create(FdroidApiInterface::class.java)
}
+ @Singleton
+ @Provides
+ fun provideFdroidWebApi(
+ okHttpClient: OkHttpClient,
+ ): FdroidWebInterface {
+ return Retrofit.Builder()
+ .baseUrl(FdroidWebInterface.BASE_URL)
+ .client(okHttpClient)
+ .build()
+ .create(FdroidWebInterface::class.java)
+ }
+
@Singleton
@Provides
fun provideEcloudApi(okHttpClient: OkHttpClient, moshi: Moshi): EcloudApiInterface {
diff --git a/app/src/main/java/foundation/e/apps/api/fdroid/FdroidWebInterface.kt b/app/src/main/java/foundation/e/apps/api/fdroid/FdroidWebInterface.kt
new file mode 100644
index 0000000000000000000000000000000000000000..14047877eb3c6f2321109ad0ccbe380ae0e297e2
--- /dev/null
+++ b/app/src/main/java/foundation/e/apps/api/fdroid/FdroidWebInterface.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019-2022 MURENA SAS
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package foundation.e.apps.api.fdroid
+
+import okhttp3.ResponseBody
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface FdroidWebInterface {
+ companion object {
+ const val BASE_URL = "https://f-droid.org/fr/packages/"
+ }
+
+ @GET("{packageName}")
+ suspend fun getFdroidApp(@Path("packageName") packageName: String): Response
+}
diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt
index 35d71d0889653d5b6fa8f9a1b7e2442817225e7a..9657199e1c0637b9c8e0ae0deb598e049d627c29 100644
--- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt
+++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt
@@ -41,6 +41,7 @@ import foundation.e.apps.api.cleanapk.CleanAPKRepository
import foundation.e.apps.api.cleanapk.data.categories.Categories
import foundation.e.apps.api.cleanapk.data.home.Home
import foundation.e.apps.api.cleanapk.data.search.Search
+import foundation.e.apps.api.fdroid.FdroidWebInterface
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedCategory
import foundation.e.apps.api.fused.data.FusedHome
@@ -74,6 +75,7 @@ class FusedAPIImpl @Inject constructor(
private val pkgManagerModule: PkgManagerModule,
private val pwaManagerModule: PWAManagerModule,
private val preferenceManagerModule: PreferenceManagerModule,
+ private val fdroidWebInterface: FdroidWebInterface,
@ApplicationContext private val context: Context
) {
@@ -1186,15 +1188,33 @@ class FusedAPIImpl @Inject constructor(
query: String,
authData: AuthData
): LiveData, Boolean>> {
- val searchResults = gPlayAPIRepository.getSearchResults(query, authData)
+ val searchResults = gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid)
return searchResults.map {
Pair(
- it.first.map { app -> app.transformToFusedApp() },
+ it.first,
it.second
)
}
}
+ /*
+ * This function will replace a GPlay app with F-Droid app if exists,
+ * else will show the GPlay app itself.
+ */
+ private suspend fun replaceWithFDroid(gPlayApp: App): FusedApp {
+ val gPlayFusedApp = gPlayApp.transformToFusedApp()
+ val response = fdroidWebInterface.getFdroidApp(gPlayFusedApp.package_name)
+ if (response.isSuccessful) {
+ val fdroidApp = getCleanApkPackageResult(gPlayFusedApp.package_name)?.apply {
+ updateSource()
+ isGplayReplaced = true
+ }
+ return fdroidApp ?: gPlayFusedApp
+ }
+
+ return gPlayFusedApp
+ }
+
/*
* Home screen-related internal functions
*/
@@ -1471,4 +1491,6 @@ class FusedAPIImpl @Inject constructor(
}
return false
}
+
+ fun isOpenSourceSelected() = preferenceManagerModule.isOpenSourceSelected()
}
diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt
index 270f65a9e98f77da1f7df45965295f70f544f347..28000e6351b6dbbda18ec8edc08830fd0a1a08da 100644
--- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt
+++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt
@@ -476,4 +476,6 @@ class FusedAPIRepository @Inject constructor(private val fusedAPIImpl: FusedAPII
hasNextStreamCluster = false
clusterPointer = 0
}
+
+ fun isOpenSourceSelected() = fusedAPIImpl.isOpenSourceSelected()
}
diff --git a/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt b/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt
index a8cf9cac2e1bda62acd88a8093cd4f68a4218651..4a12816f5bc8f79949ab14bcf67cc84287607371 100644
--- a/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt
+++ b/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt
@@ -90,5 +90,6 @@ data class FusedApp(
*
* Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720
*/
- var filterLevel: FilterLevel = FilterLevel.UNKNOWN
+ var filterLevel: FilterLevel = FilterLevel.UNKNOWN,
+ var isGplayReplaced: Boolean = false
)
diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt
index 66f7ec934259e73e5984accab95f21e150e1a92a..f68b6680e00c32401ce5f2af7472c169c82a84b9 100644
--- a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt
+++ b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt
@@ -19,6 +19,7 @@
package foundation.e.apps.api.gplay
import androidx.lifecycle.LiveData
+import androidx.lifecycle.LiveDataScope
import androidx.lifecycle.liveData
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
@@ -35,6 +36,7 @@ import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.gplayapi.helpers.SearchHelper
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.gplayapi.helpers.TopChartsHelper
+import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.gplay.utils.GPlayHttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.supervisorScope
@@ -56,7 +58,11 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli
* Sends livedata of list of apps being loaded from search and a boolean
* signifying if more data is to be loaded.
*/
- fun getSearchResults(query: String, authData: AuthData): LiveData, Boolean>> {
+ fun getSearchResults(
+ query: String,
+ authData: AuthData,
+ replaceWithFDroid: suspend (App) -> FusedApp,
+ ): LiveData, Boolean>> {
/*
* Send livedata to improve UI performance, so we don't have to wait for loading all results.
* Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171
@@ -70,7 +76,17 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli
val searchHelper = SearchHelper(authData).using(gPlayHttpClient)
val searchBundle = searchHelper.searchResults(query)
- emit(Pair(searchBundle.appList, true))
+ val initialReplacedList = mutableListOf()
+ val INITIAL_LIMIT = 4
+
+ emitReplacedList(
+ this@liveData,
+ initialReplacedList,
+ INITIAL_LIMIT,
+ replaceWithFDroid,
+ searchBundle,
+ true,
+ )
var nextSubBundleSet: MutableSet
do {
@@ -80,11 +96,63 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli
searchBundle.apply {
subBundles.clear()
subBundles.addAll(newSearchBundle.subBundles)
- appList.addAll(newSearchBundle.appList)
- emit(Pair(searchBundle.appList, nextSubBundleSet.isNotEmpty()))
+ emitReplacedList(
+ this@liveData,
+ initialReplacedList,
+ INITIAL_LIMIT,
+ replaceWithFDroid,
+ newSearchBundle,
+ nextSubBundleSet.isNotEmpty(),
+ )
}
}
} while (nextSubBundleSet.isNotEmpty())
+
+ /*
+ * If initialReplacedList size is less than INITIAL_LIMIT,
+ * it means the results were very less and nothing has been emitted so far.
+ * Hence emit the list.
+ */
+ if (initialReplacedList.size < INITIAL_LIMIT) {
+ emit(Pair(initialReplacedList, false))
+ }
+ }
+ }
+ }
+
+ private suspend fun emitReplacedList(
+ scope: LiveDataScope, Boolean>>,
+ accumulationList: MutableList,
+ accumulationLimit: Int,
+ replaceFunction: suspend (App) -> FusedApp,
+ searchBundle: SearchBundle,
+ moreToEmit: Boolean,
+ ) {
+ searchBundle.appList.forEach {
+ val replacedApp = replaceFunction(it)
+ when {
+ accumulationList.size < accumulationLimit - 1 -> {
+ /*
+ * If initial limit is 4, add apps to list (without emitting)
+ * till 2 apps.
+ */
+ accumulationList.add(replacedApp)
+ }
+ accumulationList.size == accumulationLimit - 1 -> {
+ /*
+ * If initial limit is 4, and we have reached till 3 apps,
+ * add the 4th app and emit the list.
+ */
+ accumulationList.add(replacedApp)
+ scope.emit(Pair(accumulationList, moreToEmit))
+ }
+ accumulationList.size == accumulationLimit -> {
+ /*
+ * If initial limit is 4, and we have emitted 4 apps,
+ * for all rest of the apps, emit each app one by one.
+ */
+ scope.emit(Pair(listOf(replacedApp), moreToEmit))
+ }
}
}
}
@@ -113,7 +181,14 @@ class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpCli
val downloadData = mutableListOf()
withContext(Dispatchers.IO) {
val purchaseHelper = PurchaseHelper(authData).using(gPlayHttpClient)
- downloadData.addAll(purchaseHelper.getOnDemandModule(packageName, moduleName, versionCode, offerType))
+ downloadData.addAll(
+ purchaseHelper.getOnDemandModule(
+ packageName,
+ moduleName,
+ versionCode,
+ offerType
+ )
+ )
}
return downloadData
}
diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt
index f8c735a7d7249d3dfd4d9e14af3768fc1a76c141..4475cb03acf7135b2d9c3fbe5ad1bdfc9b7d9121 100644
--- a/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt
+++ b/app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIRepository.kt
@@ -27,6 +27,7 @@ import com.aurora.gplayapi.data.models.File
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.TopChartsHelper
+import foundation.e.apps.api.fused.data.FusedApp
import javax.inject.Inject
class GPlayAPIRepository @Inject constructor(private val gPlayAPIImpl: GPlayAPIImpl) {
@@ -35,8 +36,12 @@ class GPlayAPIRepository @Inject constructor(private val gPlayAPIImpl: GPlayAPII
return gPlayAPIImpl.getSearchSuggestions(query, authData)
}
- fun getSearchResults(query: String, authData: AuthData): LiveData, Boolean>> {
- return gPlayAPIImpl.getSearchResults(query, authData)
+ fun getSearchResults(
+ query: String,
+ authData: AuthData,
+ replaceWithFDroid: suspend (App) -> FusedApp,
+ ): LiveData, Boolean>> {
+ return gPlayAPIImpl.getSearchResults(query, authData, replaceWithFDroid)
}
suspend fun getOnDemandModule(
diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt
index bd4414dd6c298cfaabb7bc66ed0d6b910559dd40..62d3a62f66a87740781cda0be7317db3f1d940b9 100644
--- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt
+++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt
@@ -205,9 +205,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) {
// Privacy widgets
updatePrivacyPanel()
- if (appInfoFetchViewModel.isAppInBlockedList(it)) {
- binding.snackbarLayout.visibility = View.VISIBLE
- }
+ showWarningMessage(it)
fetchAppTracker(it)
observeDownloadList()
@@ -215,6 +213,20 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) {
stopLoadingUI()
}
+ private fun showWarningMessage(it: FusedApp) {
+ if (appInfoFetchViewModel.isAppInBlockedList(it)) {
+ binding.snackbarLayout.visibility = View.VISIBLE
+ } else if (args.isGplayReplaced && !applicationViewModel.isOpenSourceSelected()) {
+ binding.duplicateAppCardview.visibility = View.VISIBLE
+ binding.duplicateAppCardview.setOnClickListener {
+ ApplicationDialogFragment(
+ title = getString(R.string.open_source_apps),
+ message = getString(R.string.duplicate_app_from_sources)
+ ).show(childFragmentManager, TAG)
+ }
+ }
+ }
+
private fun observeDownloadList() {
mainActivityViewModel.downloadList.removeObservers(viewLifecycleOwner)
mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list ->
diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt b/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt
index 6366fdb286d560717c80adb63474cf5d4ae50fea..d4e9b43c0bb30f0fecd7352cf65e7925420a6b06 100644
--- a/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt
+++ b/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt
@@ -181,4 +181,6 @@ class ApplicationViewModel @Inject constructor(
?: fusedAPIRepository.getFusedAppInstallationStatus(app)
}
}
+
+ fun isOpenSourceSelected() = fusedAPIRepository.isOpenSourceSelected()
}
diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationDiffUtil.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationDiffUtil.kt
index e1c2c3a65b37847884d17476bd7de5bfba3911a7..583c38d44d60b0b2f65a359e60e4757d37c282f9 100644
--- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationDiffUtil.kt
+++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationDiffUtil.kt
@@ -19,6 +19,7 @@ package foundation.e.apps.applicationlist
import androidx.recyclerview.widget.DiffUtil
import foundation.e.apps.api.fused.data.FusedApp
+import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL
class ApplicationDiffUtil : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: FusedApp, newItem: FusedApp): Boolean {
@@ -27,29 +28,29 @@ class ApplicationDiffUtil : DiffUtil.ItemCallback() {
override fun areContentsTheSame(oldItem: FusedApp, newItem: FusedApp): Boolean {
return oldItem._id == newItem._id &&
- oldItem.appSize.contentEquals(newItem.appSize) &&
- oldItem.author.contentEquals(newItem.author) &&
- oldItem.category.contentEquals(newItem.category) &&
- oldItem.description.contentEquals(newItem.description) &&
- oldItem.icon_image_path.contentEquals(newItem.icon_image_path) &&
- oldItem.last_modified.contentEquals(newItem.last_modified) &&
- oldItem.latest_version_code == newItem.latest_version_code &&
- oldItem.latest_version_number.contentEquals(newItem.latest_version_number) &&
- oldItem.licence.contentEquals(newItem.licence) &&
- oldItem.appSize.contentEquals(newItem.appSize) &&
- oldItem.name.contentEquals(newItem.name) &&
- oldItem.offer_type == newItem.offer_type &&
- oldItem.origin == newItem.origin &&
- oldItem.other_images_path == newItem.other_images_path &&
- oldItem.package_name.contentEquals(newItem.package_name) &&
- oldItem.perms == newItem.perms &&
- oldItem.ratings == newItem.ratings &&
- oldItem.shareUrl.contentEquals(newItem.shareUrl) &&
- oldItem.source.contentEquals(newItem.source) &&
- oldItem.status == newItem.status &&
- oldItem.trackers == newItem.trackers &&
- oldItem.url.contentEquals(newItem.url) &&
- oldItem.isFree == newItem.isFree &&
- oldItem.is_pwa == newItem.is_pwa
+ oldItem.appSize.contentEquals(newItem.appSize) &&
+ oldItem.author.contentEquals(newItem.author) &&
+ oldItem.category.contentEquals(newItem.category) &&
+ oldItem.description.contentEquals(newItem.description) &&
+ oldItem.icon_image_path.contentEquals(newItem.icon_image_path) &&
+ oldItem.last_modified.contentEquals(newItem.last_modified) &&
+ oldItem.latest_version_code == newItem.latest_version_code &&
+ oldItem.latest_version_number.contentEquals(newItem.latest_version_number) &&
+ oldItem.licence.contentEquals(newItem.licence) &&
+ oldItem.appSize.contentEquals(newItem.appSize) &&
+ oldItem.name.contentEquals(newItem.name) &&
+ oldItem.offer_type == newItem.offer_type &&
+ oldItem.origin == newItem.origin &&
+ oldItem.other_images_path == newItem.other_images_path &&
+ oldItem.package_name.contentEquals(newItem.package_name) &&
+ oldItem.perms == newItem.perms &&
+ oldItem.ratings == newItem.ratings &&
+ oldItem.shareUrl.contentEquals(newItem.shareUrl) &&
+ oldItem.source.contentEquals(newItem.source) &&
+ oldItem.status == newItem.status &&
+ ((oldItem.trackers == LIST_OF_NULL && newItem.trackers.isEmpty()) || oldItem.trackers == newItem.trackers) &&
+ oldItem.url.contentEquals(newItem.url) &&
+ oldItem.isFree == newItem.isFree &&
+ oldItem.is_pwa == newItem.is_pwa
}
}
diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt
index 786571b00fa892944e030e3816a3893bf3012bfa..f056b13acbe11cf59e2933ea35d3c6467f212108 100644
--- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt
+++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt
@@ -218,6 +218,7 @@ class ApplicationListRVAdapter(
searchApp.package_name,
searchApp.origin,
catText,
+ searchApp.isGplayReplaced
)
}
R.id.updatesFragment -> {
diff --git a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt
index aa0f9697ec08812597453ce9af00d7fdf3afdbe3..deab32583a5e2295681b2ffaf90263a778674deb 100644
--- a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt
+++ b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt
@@ -19,6 +19,7 @@
package foundation.e.apps.search
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.SearchSuggestEntry
@@ -43,11 +44,18 @@ class SearchViewModel @Inject constructor(
val searchSuggest: MutableLiveData?> = MutableLiveData()
val searchResult: MutableLiveData, Boolean>>> =
MutableLiveData()
+ private var searchResultLiveData: LiveData, Boolean>>> =
+ MutableLiveData()
fun getSearchSuggestions(query: String, gPlayAuth: AuthObject.GPlayAuth) {
viewModelScope.launch(Dispatchers.IO) {
if (gPlayAuth.result.isSuccess())
- searchSuggest.postValue(fusedAPIRepository.getSearchSuggestions(query, gPlayAuth.result.data!!))
+ searchSuggest.postValue(
+ fusedAPIRepository.getSearchSuggestions(
+ query,
+ gPlayAuth.result.data!!
+ )
+ )
}
}
@@ -82,7 +90,9 @@ class SearchViewModel @Inject constructor(
*/
fun getSearchResults(query: String, authData: AuthData, lifecycleOwner: LifecycleOwner) {
viewModelScope.launch(Dispatchers.Main) {
- fusedAPIRepository.getSearchResults(query, authData).observe(lifecycleOwner) {
+ searchResultLiveData.removeObservers(lifecycleOwner)
+ searchResultLiveData = fusedAPIRepository.getSearchResults(query, authData)
+ searchResultLiveData.observe(lifecycleOwner) {
searchResult.postValue(it)
if (!it.isSuccess()) {
diff --git a/app/src/main/res/drawable/ic_warning_black.xml b/app/src/main/res/drawable/ic_warning_black.xml
new file mode 100644
index 0000000000000000000000000000000000000000..90f73c77a9025cc09270fe38ac662151add5e564
--- /dev/null
+++ b/app/src/main/res/drawable/ic_warning_black.xml
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_application.xml b/app/src/main/res/layout/fragment_application.xml
index 7b4af2745de4ec0751064521845a56a7f77761ba..62958a2a393623ae3426cf99da30b138ada0be89 100644
--- a/app/src/main/res/layout/fragment_application.xml
+++ b/app/src/main/res/layout/fragment_application.xml
@@ -89,6 +89,36 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/navigation/navigation_resource.xml b/app/src/main/res/navigation/navigation_resource.xml
index 1c7d6c2909746eeb125cb49186d5ac943fc2ad3b..d15437ba1ca82f16b18c93c0e91ab554b094f628 100644
--- a/app/src/main/res/navigation/navigation_resource.xml
+++ b/app/src/main/res/navigation/navigation_resource.xml
@@ -109,6 +109,10 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 44b0740706f7059cdd594b38d95382fc7cf4e946..1deed041e66a81039ace74e10774b6bbc7bd4542 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -113,6 +113,10 @@
Something went wrong!
Show more
Cannot show Google Play app when only open source apps are allowed.
+ Why am I seeing the Open Source version?
+ Open Source apps
+ Some proprietary apps may also have an Open Source version. Whenever this happens App Lounge shows the Open Source version only, in order to avoid duplicates.
+
diff --git a/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt
index ac6c7281d654b2bdc23352173ccf01a72020bed6..646b84fad6f0c241f16dde4fb2f5687d13aac5f4 100644
--- a/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt
+++ b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt
@@ -20,6 +20,7 @@ package foundation.e.apps
import android.content.Context
import android.text.format.Formatter
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.data.models.App
@@ -29,6 +30,7 @@ import foundation.e.apps.api.cleanapk.CleanAPKInterface
import foundation.e.apps.api.cleanapk.CleanAPKRepository
import foundation.e.apps.api.cleanapk.data.categories.Categories
import foundation.e.apps.api.cleanapk.data.search.Search
+import foundation.e.apps.api.fdroid.FdroidWebInterface
import foundation.e.apps.api.fused.FusedAPIImpl
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedHome
@@ -88,6 +90,9 @@ class FusedApiImplTest {
@Mock
private lateinit var gPlayAPIRepository: GPlayAPIRepository
+ @Mock
+ private lateinit var fdroidWebInterface: FdroidWebInterface
+
private lateinit var preferenceManagerModule: FakePreferenceModule
private lateinit var formatterMocked: MockedStatic
@@ -107,6 +112,7 @@ class FusedApiImplTest {
pkgManagerModule,
pwaManagerModule,
preferenceManagerModule,
+ fdroidWebInterface,
context
)
}
@@ -759,9 +765,9 @@ class FusedApiImplTest {
preferenceManagerModule.isPWASelectedFake = true
preferenceManagerModule.isOpenSourceelectedFake = true
preferenceManagerModule.isGplaySelectedFake = true
- val gplayLivedata = MutableLiveData(
+ val gplayLivedata: LiveData, Boolean>> = MutableLiveData(
Pair(
- listOf(App("a.b.c"), App("c.d.e"), App("d.e.f"), App("d.e.g")), false
+ listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f"), FusedApp("d.e.g")), false
)
)
@@ -780,7 +786,7 @@ class FusedApiImplTest {
packageNameSearchResponse: Response?,
authData: AuthData,
gplayPackageResult: App,
- gplayLivedata: MutableLiveData, Boolean>>,
+ gplayLivedata: LiveData, Boolean>>?,
willThrowException: Boolean = false
) {
Mockito.`when`(pwaManagerModule.getPwaStatus(any())).thenReturn(Status.UNAVAILABLE)
@@ -811,7 +817,18 @@ class FusedApiImplTest {
source = CleanAPKInterface.APP_SOURCE_ANY
)
).thenReturn(packageNameSearchResponse)
- Mockito.`when`(gPlayAPIRepository.getSearchResults(eq("com.search.package"), eq(authData)))
+
+ suspend fun replaceWithFDroid(gPlayApp: App): FusedApp {
+ return FusedApp(gPlayApp.id.toString(), gPlayApp.packageName)
+ }
+
+ Mockito.`when`(
+ gPlayAPIRepository.getSearchResults(
+ eq("com.search.package"),
+ eq(authData),
+ eq(::replaceWithFDroid)
+ )
+ )
.thenReturn(gplayLivedata)
}
@@ -846,7 +863,7 @@ class FusedApiImplTest {
val gplayPackageResult = App("com.search.package")
val gplayLivedata =
- MutableLiveData(Pair(listOf(App("a.b.c"), App("c.d.e"), App("d.e.f")), false))
+ MutableLiveData(Pair(listOf(FusedApp("a.b.c"), FusedApp("c.d.e"), FusedApp("d.e.f")), false))
setupMockingSearchApp(
packageNameSearchResponse, AUTH_DATA, gplayPackageResult, gplayLivedata, true