Loading app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +22 −4 Original line number Diff line number Diff line Loading @@ -19,9 +19,24 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( private val exodusTrackerApi: ExodusTrackerApi, private val trackerDao: TrackerDao ) : IAppPrivacyInfoRepository { companion object { private const val MAX_TRACKER_SCORE = 9 private const val MIN_TRACKER_SCORE = 0 private const val MAX_PERMISSION_SCORE = 10 private const val MIN_PERMISSION_SCORE = 0 private const val THRESHOLD_OF_NON_ZERO_TRACKER_SCORE = 5 private const val THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE = 9 private const val FACTOR_OF_PERMISSION_SCORE = 0.2 private const val DIVIDER_OF_PERMISSION_SCORE = 2.0 } private var trackers: List<Tracker> = listOf() override suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> { override suspend fun getAppPrivacyInfo( fusedApp: FusedApp, appHandle: String ): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) Loading @@ -29,7 +44,8 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( val appTrackerInfoResult = getResult { exodusTrackerApi.getTrackerInfoOfApp(appHandle) } if (appTrackerInfoResult.isSuccess()) { val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) return appPrivacyPrivacyInfoResult } Loading Loading @@ -136,10 +152,12 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers return if (numberOfTrackers > THRESHOLD_OF_NON_ZERO_TRACKER_SCORE) MIN_TRACKER_SCORE else MAX_TRACKER_SCORE - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() return if (numberOfPermission > THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE) MIN_PERMISSION_SCORE else round( FACTOR_OF_PERMISSION_SCORE * ceil((MAX_PERMISSION_SCORE - numberOfPermission) / DIVIDER_OF_PERMISSION_SCORE) ).toInt() } } app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +184 −141 Original line number Diff line number Diff line Loading @@ -73,8 +73,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private val args: ApplicationFragmentArgs by navArgs() private val TAG = ApplicationFragment::class.java.simpleName private var _binding: FragmentApplicationBinding? = null private val binding get() = _binding!! /* Loading Loading @@ -104,6 +104,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private var isDetailsLoaded = false private lateinit var screenshotsRVAdapter: ApplicationScreenshotsRVAdapter @Inject lateinit var pkgManagerModule: PkgManagerModule Loading Loading @@ -139,32 +141,27 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { refreshDataOrRefreshToken(mainActivityViewModel) } val startDestination = findNavController().graph.startDestination if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() view.findNavController().navigate(action) } } else { binding.toolbar.setNavigationOnClickListener { view.findNavController().navigateUp() } } val notAvailable = getString(R.string.not_available) setupToolbar(view) val screenshotsRVAdapter = ApplicationScreenshotsRVAdapter(origin) binding.recyclerView.apply { adapter = screenshotsRVAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) } setupScreenshotRVAdapter() binding.applicationLayout.visibility = View.INVISIBLE applicationViewModel.fusedApp.observe(viewLifecycleOwner) { resultPair -> updateUi(resultPair) } applicationViewModel.errorMessageLiveData.observe(viewLifecycleOwner) { (requireActivity() as MainActivity).showSnackbarMessage(getString(it)) } } private fun updateUi( resultPair: Pair<FusedApp, ResultStatus>, ) { if (resultPair.second != ResultStatus.OK) { onTimeout() return@observe return } /* Loading @@ -187,59 +184,32 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { screenshotsRVAdapter.setData(it.other_images_path) // Title widgets binding.titleInclude.apply { applicationIcon = appIcon appName.text = it.name appInfoFetchViewModel.getAuthorName(it).observe(viewLifecycleOwner) { appAuthor.text = it } categoryTitle.text = it.category if (origin == Origin.CLEANAPK) { appIcon.load(CleanAPKInterface.ASSET_URL + it.icon_image_path) } else { appIcon.load(it.icon_image_path) } } updateAppTitlePanel(it) binding.downloadInclude.appSize.text = it.appSize // Ratings widgets binding.ratingsInclude.apply { if (it.ratings.usageQualityScore != -1.0) { val rating = applicationViewModel.handleRatingFormat(it.ratings.usageQualityScore) appRating.text = getString( R.string.rating_out_of, rating ) updateAppRating(it) appRating.setCompoundDrawablesWithIntrinsicBounds( null, null, getRatingDrawable(rating), null ) appRating.compoundDrawablePadding = 15 } appRatingLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_star, getString(R.string.rating), getString(R.string.rating_description) ).show(childFragmentManager, TAG) updateAppDescriptionText(it) // Information widgets updateAppInformation(it) // Privacy widgets updatePrivacyPanel() if (appInfoFetchViewModel.isAppInBlockedList(it)) { binding.snackbarLayout.visibility = View.VISIBLE } fetchAppTracker(it) appPrivacyScoreLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_lock, getString(R.string.privacy_score), getString( R.string.privacy_description, PRIVACY_SCORE_SOURCE_CODE_URL, EXODUS_URL, PRIVACY_GUIDELINE_URL ) ).show(childFragmentManager, TAG) mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> applicationViewModel.updateApplicationStatus(list) } } private fun updateAppDescriptionText(it: FusedApp) { binding.appDescription.text = Html.fromHtml(it.description, Html.FROM_HTML_MODE_COMPACT) Loading @@ -248,26 +218,9 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { ApplicationFragmentDirections.actionApplicationFragmentToDescriptionFragment(it.description) view.findNavController().navigate(action) } // Information widgets binding.infoInclude.apply { appUpdatedOn.text = getString( R.string.updated_on, if (origin == Origin.CLEANAPK) it.updatedOn else it.last_modified ) appRequires.text = getString(R.string.min_android_version, notAvailable) appVersion.text = getString( R.string.version, if (it.latest_version_number == "-1") notAvailable else it.latest_version_number ) appLicense.text = getString( R.string.license, if (it.licence.isBlank() or (it.licence == "unknown")) notAvailable else it.licence ) appPackageName.text = getString(R.string.package_name, it.package_name) } // Privacy widgets private fun updatePrivacyPanel() { binding.privacyInclude.apply { appPermissions.setOnClickListener { _ -> ApplicationDialogFragment( Loading Loading @@ -299,19 +252,103 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { ).show(childFragmentManager, TAG) } } } if (appInfoFetchViewModel.isAppInBlockedList(it)) { binding.snackbarLayout.visibility = View.VISIBLE private fun updateAppInformation( it: FusedApp, ) { binding.infoInclude.apply { appUpdatedOn.text = getString( R.string.updated_on, if (origin == Origin.CLEANAPK) it.updatedOn else it.last_modified ) val notAvailable = getString(R.string.not_available) appRequires.text = getString(R.string.min_android_version, notAvailable) appVersion.text = getString( R.string.version, if (it.latest_version_number == "-1") notAvailable else it.latest_version_number ) appLicense.text = getString( R.string.license, if (it.licence.isBlank() or (it.licence == "unknown")) notAvailable else it.licence ) appPackageName.text = getString(R.string.package_name, it.package_name) } } fetchAppTracker(it) mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> applicationViewModel.updateApplicationStatus(list) private fun updateAppRating(it: FusedApp) { binding.ratingsInclude.apply { if (it.ratings.usageQualityScore != -1.0) { val rating = applicationViewModel.handleRatingFormat(it.ratings.usageQualityScore) appRating.text = getString( R.string.rating_out_of, rating ) appRating.setCompoundDrawablesWithIntrinsicBounds( null, null, getRatingDrawable(rating), null ) appRating.compoundDrawablePadding = 15 } appRatingLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_star, getString(R.string.rating), getString(R.string.rating_description) ).show(childFragmentManager, TAG) } applicationViewModel.errorMessageLiveData.observe(viewLifecycleOwner) { (requireActivity() as MainActivity).showSnackbarMessage(getString(it)) appPrivacyScoreLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_lock, getString(R.string.privacy_score), getString( R.string.privacy_description, PRIVACY_SCORE_SOURCE_CODE_URL, EXODUS_URL, PRIVACY_GUIDELINE_URL ) ).show(childFragmentManager, TAG) } } } private fun updateAppTitlePanel(it: FusedApp) { binding.titleInclude.apply { applicationIcon = appIcon appName.text = it.name appInfoFetchViewModel.getAuthorName(it).observe(viewLifecycleOwner) { appAuthor.text = it } categoryTitle.text = it.category if (origin == Origin.CLEANAPK) { appIcon.load(CleanAPKInterface.ASSET_URL + it.icon_image_path) } else { appIcon.load(it.icon_image_path) } } } private fun setupScreenshotRVAdapter() { screenshotsRVAdapter = ApplicationScreenshotsRVAdapter(origin) binding.recyclerView.apply { adapter = screenshotsRVAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) } } private fun setupToolbar(view: View) { val startDestination = findNavController().graph.startDestination if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() view.findNavController().navigate(action) } } else { binding.toolbar.setNavigationOnClickListener { view.findNavController().navigateUp() } } } Loading Loading @@ -381,7 +418,12 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { downloadPB, appSize ) Status.UNAVAILABLE -> handleUnavaiable(installButton, fusedApp, downloadPB, appSize) Status.UNAVAILABLE -> handleUnavaiable( installButton, fusedApp, downloadPB, appSize ) Status.QUEUED, Status.AWAITING, Status.DOWNLOADED -> handleQueued( installButton, fusedApp, Loading Loading @@ -669,7 +711,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } private fun updatePrivacyScore() { val privacyScore = privacyInfoViewModel.getPrivacyScore(applicationViewModel.fusedApp.value?.first) val privacyScore = privacyInfoViewModel.getPrivacyScore(applicationViewModel.fusedApp.value?.first) if (privacyScore != -1) { val appPrivacyScore = binding.ratingsInclude.appPrivacyScore appPrivacyScore.text = getString( Loading app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +10 −7 Original line number Diff line number Diff line Loading @@ -78,13 +78,7 @@ class ApplicationListFragment : super.onViewCreated(view, savedInstanceState) _binding = FragmentApplicationListBinding.bind(view) binding.toolbarTitleTV.text = args.translation binding.toolbar.apply { setNavigationOnClickListener { view.findNavController().navigate(R.id.categoriesFragment) } } updateToolbar(view) setupRecyclerView(view) observeAppListLiveData() Loading @@ -100,6 +94,15 @@ class ApplicationListFragment : } } private fun updateToolbar(view: View) { binding.toolbarTitleTV.text = args.translation binding.toolbar.apply { setNavigationOnClickListener { view.findNavController().navigate(R.id.categoriesFragment) } } } private fun setupRecyclerView(view: View) { val recyclerView = initRecyclerView() findNavController().currentDestination?.id?.let { Loading app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +91 −60 Original line number Diff line number Diff line Loading @@ -123,79 +123,110 @@ class ApplicationListRVAdapter( hidePrivacyScore() } applicationList.setOnClickListener { val action = when (currentDestinationId) { R.id.applicationListFragment -> { ApplicationListFragmentDirections.actionApplicationListFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) handleAppItemClick(searchApp, view) } R.id.searchFragment -> { SearchFragmentDirections.actionSearchFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) updateAppInfo(searchApp) updateRating(searchApp) updatePrivacyScore(searchApp, view) updateSourceTag(searchApp) setAppIcon(searchApp, shimmerDrawable) removeIsPurchasedObserver(holder) if (appInfoFetchViewModel.isAppInBlockedList(searchApp)) { setupShowMoreButton() } else { mainActivityViewModel.verifyUiFilter(searchApp) { setupInstallButton(searchApp, view, holder) } R.id.updatesFragment -> { UpdatesFragmentDirections.actionUpdatesFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } else -> null showCalculatedPrivacyScoreData(searchApp, view) } } private fun ApplicationListItemBinding.setAppIcon( searchApp: FusedApp, shimmerDrawable: ShimmerDrawable ) { when (searchApp.origin) { Origin.GPLAY -> { appIcon.load(searchApp.icon_image_path) { placeholder(shimmerDrawable) } action?.let { direction -> view.findNavController().navigate(direction) } } Origin.CLEANAPK -> { appIcon.load(CleanAPKInterface.ASSET_URL + searchApp.icon_image_path) { placeholder(shimmerDrawable) } } else -> Log.wtf(TAG, "${searchApp.package_name} is from an unknown origin") } } private fun ApplicationListItemBinding.updateAppInfo(searchApp: FusedApp) { appTitle.text = searchApp.name appInfoFetchViewModel.getAuthorName(searchApp).observe(lifecycleOwner!!) { appAuthor.text = it } } private fun ApplicationListItemBinding.updateRating(searchApp: FusedApp) { if (searchApp.ratings.usageQualityScore != -1.0) { appRating.text = searchApp.ratings.usageQualityScore.toString() appRatingBar.rating = searchApp.ratings.usageQualityScore.toFloat() } } private fun ApplicationListItemBinding.updatePrivacyScore( searchApp: FusedApp, view: View ) { if (searchApp.ratings.privacyScore != -1.0) { appPrivacyScore.text = view.context.getString( R.string.privacy_rating_out_of, searchApp.ratings.privacyScore.toInt().toString() ) } } private fun ApplicationListItemBinding.updateSourceTag(searchApp: FusedApp) { if (searchApp.source.isEmpty()) { sourceTag.visibility = View.INVISIBLE } else { sourceTag.visibility = View.VISIBLE } sourceTag.text = searchApp.source when (searchApp.origin) { Origin.GPLAY -> { appIcon.load(searchApp.icon_image_path) { placeholder(shimmerDrawable) } } Origin.CLEANAPK -> { appIcon.load(CleanAPKInterface.ASSET_URL + searchApp.icon_image_path) { placeholder(shimmerDrawable) } } else -> Log.wtf(TAG, "${searchApp.package_name} is from an unknown origin") } removeIsPurchasedObserver(holder) if (appInfoFetchViewModel.isAppInBlockedList(searchApp)) { setupShowMoreButton() } else { mainActivityViewModel.verifyUiFilter(searchApp) { setupInstallButton(searchApp, view, holder) private fun handleAppItemClick( searchApp: FusedApp, view: View ) { val action = when (currentDestinationId) { R.id.applicationListFragment -> { ApplicationListFragmentDirections.actionApplicationListFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } R.id.searchFragment -> { SearchFragmentDirections.actionSearchFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } showCalculatedPrivacyScoreData(searchApp, view) R.id.updatesFragment -> { UpdatesFragmentDirections.actionUpdatesFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } else -> null } action?.let { direction -> view.findNavController().navigate(direction) } } private fun removeIsPurchasedObserver(holder: ViewHolder) { Loading app/src/main/java/foundation/e/apps/categories/model/CategoriesRVAdapter.kt +21 −13 Original line number Diff line number Diff line Loading @@ -59,12 +59,21 @@ class CategoriesRVAdapter : ) holder.itemView.findNavController().navigate(direction) } loadCategoryIcon(position) categoryTitle.text = oldList[position].title updateTag(position) } } private fun CategoriesListItemBinding.loadCategoryIcon(position: Int) { if (oldList[position].drawable != -1) { categoryIcon.load(oldList[position].drawable) } else { categoryIcon.load(oldList[position].imageUrl) } categoryTitle.text = oldList[position].title } private fun CategoriesListItemBinding.updateTag(position: Int) { val tag = oldList[position].tag if (tag.displayTag.isNotBlank()) { categoryTag.visibility = View.VISIBLE Loading @@ -74,7 +83,6 @@ class CategoriesRVAdapter : categoryTag.text = "" } } } override fun getItemCount(): Int { return oldList.size Loading Loading
app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +22 −4 Original line number Diff line number Diff line Loading @@ -19,9 +19,24 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( private val exodusTrackerApi: ExodusTrackerApi, private val trackerDao: TrackerDao ) : IAppPrivacyInfoRepository { companion object { private const val MAX_TRACKER_SCORE = 9 private const val MIN_TRACKER_SCORE = 0 private const val MAX_PERMISSION_SCORE = 10 private const val MIN_PERMISSION_SCORE = 0 private const val THRESHOLD_OF_NON_ZERO_TRACKER_SCORE = 5 private const val THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE = 9 private const val FACTOR_OF_PERMISSION_SCORE = 0.2 private const val DIVIDER_OF_PERMISSION_SCORE = 2.0 } private var trackers: List<Tracker> = listOf() override suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> { override suspend fun getAppPrivacyInfo( fusedApp: FusedApp, appHandle: String ): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) Loading @@ -29,7 +44,8 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( val appTrackerInfoResult = getResult { exodusTrackerApi.getTrackerInfoOfApp(appHandle) } if (appTrackerInfoResult.isSuccess()) { val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) return appPrivacyPrivacyInfoResult } Loading Loading @@ -136,10 +152,12 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers return if (numberOfTrackers > THRESHOLD_OF_NON_ZERO_TRACKER_SCORE) MIN_TRACKER_SCORE else MAX_TRACKER_SCORE - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() return if (numberOfPermission > THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE) MIN_PERMISSION_SCORE else round( FACTOR_OF_PERMISSION_SCORE * ceil((MAX_PERMISSION_SCORE - numberOfPermission) / DIVIDER_OF_PERMISSION_SCORE) ).toInt() } }
app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +184 −141 Original line number Diff line number Diff line Loading @@ -73,8 +73,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private val args: ApplicationFragmentArgs by navArgs() private val TAG = ApplicationFragment::class.java.simpleName private var _binding: FragmentApplicationBinding? = null private val binding get() = _binding!! /* Loading Loading @@ -104,6 +104,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private var isDetailsLoaded = false private lateinit var screenshotsRVAdapter: ApplicationScreenshotsRVAdapter @Inject lateinit var pkgManagerModule: PkgManagerModule Loading Loading @@ -139,32 +141,27 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { refreshDataOrRefreshToken(mainActivityViewModel) } val startDestination = findNavController().graph.startDestination if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() view.findNavController().navigate(action) } } else { binding.toolbar.setNavigationOnClickListener { view.findNavController().navigateUp() } } val notAvailable = getString(R.string.not_available) setupToolbar(view) val screenshotsRVAdapter = ApplicationScreenshotsRVAdapter(origin) binding.recyclerView.apply { adapter = screenshotsRVAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) } setupScreenshotRVAdapter() binding.applicationLayout.visibility = View.INVISIBLE applicationViewModel.fusedApp.observe(viewLifecycleOwner) { resultPair -> updateUi(resultPair) } applicationViewModel.errorMessageLiveData.observe(viewLifecycleOwner) { (requireActivity() as MainActivity).showSnackbarMessage(getString(it)) } } private fun updateUi( resultPair: Pair<FusedApp, ResultStatus>, ) { if (resultPair.second != ResultStatus.OK) { onTimeout() return@observe return } /* Loading @@ -187,59 +184,32 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { screenshotsRVAdapter.setData(it.other_images_path) // Title widgets binding.titleInclude.apply { applicationIcon = appIcon appName.text = it.name appInfoFetchViewModel.getAuthorName(it).observe(viewLifecycleOwner) { appAuthor.text = it } categoryTitle.text = it.category if (origin == Origin.CLEANAPK) { appIcon.load(CleanAPKInterface.ASSET_URL + it.icon_image_path) } else { appIcon.load(it.icon_image_path) } } updateAppTitlePanel(it) binding.downloadInclude.appSize.text = it.appSize // Ratings widgets binding.ratingsInclude.apply { if (it.ratings.usageQualityScore != -1.0) { val rating = applicationViewModel.handleRatingFormat(it.ratings.usageQualityScore) appRating.text = getString( R.string.rating_out_of, rating ) updateAppRating(it) appRating.setCompoundDrawablesWithIntrinsicBounds( null, null, getRatingDrawable(rating), null ) appRating.compoundDrawablePadding = 15 } appRatingLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_star, getString(R.string.rating), getString(R.string.rating_description) ).show(childFragmentManager, TAG) updateAppDescriptionText(it) // Information widgets updateAppInformation(it) // Privacy widgets updatePrivacyPanel() if (appInfoFetchViewModel.isAppInBlockedList(it)) { binding.snackbarLayout.visibility = View.VISIBLE } fetchAppTracker(it) appPrivacyScoreLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_lock, getString(R.string.privacy_score), getString( R.string.privacy_description, PRIVACY_SCORE_SOURCE_CODE_URL, EXODUS_URL, PRIVACY_GUIDELINE_URL ) ).show(childFragmentManager, TAG) mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> applicationViewModel.updateApplicationStatus(list) } } private fun updateAppDescriptionText(it: FusedApp) { binding.appDescription.text = Html.fromHtml(it.description, Html.FROM_HTML_MODE_COMPACT) Loading @@ -248,26 +218,9 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { ApplicationFragmentDirections.actionApplicationFragmentToDescriptionFragment(it.description) view.findNavController().navigate(action) } // Information widgets binding.infoInclude.apply { appUpdatedOn.text = getString( R.string.updated_on, if (origin == Origin.CLEANAPK) it.updatedOn else it.last_modified ) appRequires.text = getString(R.string.min_android_version, notAvailable) appVersion.text = getString( R.string.version, if (it.latest_version_number == "-1") notAvailable else it.latest_version_number ) appLicense.text = getString( R.string.license, if (it.licence.isBlank() or (it.licence == "unknown")) notAvailable else it.licence ) appPackageName.text = getString(R.string.package_name, it.package_name) } // Privacy widgets private fun updatePrivacyPanel() { binding.privacyInclude.apply { appPermissions.setOnClickListener { _ -> ApplicationDialogFragment( Loading Loading @@ -299,19 +252,103 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { ).show(childFragmentManager, TAG) } } } if (appInfoFetchViewModel.isAppInBlockedList(it)) { binding.snackbarLayout.visibility = View.VISIBLE private fun updateAppInformation( it: FusedApp, ) { binding.infoInclude.apply { appUpdatedOn.text = getString( R.string.updated_on, if (origin == Origin.CLEANAPK) it.updatedOn else it.last_modified ) val notAvailable = getString(R.string.not_available) appRequires.text = getString(R.string.min_android_version, notAvailable) appVersion.text = getString( R.string.version, if (it.latest_version_number == "-1") notAvailable else it.latest_version_number ) appLicense.text = getString( R.string.license, if (it.licence.isBlank() or (it.licence == "unknown")) notAvailable else it.licence ) appPackageName.text = getString(R.string.package_name, it.package_name) } } fetchAppTracker(it) mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> applicationViewModel.updateApplicationStatus(list) private fun updateAppRating(it: FusedApp) { binding.ratingsInclude.apply { if (it.ratings.usageQualityScore != -1.0) { val rating = applicationViewModel.handleRatingFormat(it.ratings.usageQualityScore) appRating.text = getString( R.string.rating_out_of, rating ) appRating.setCompoundDrawablesWithIntrinsicBounds( null, null, getRatingDrawable(rating), null ) appRating.compoundDrawablePadding = 15 } appRatingLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_star, getString(R.string.rating), getString(R.string.rating_description) ).show(childFragmentManager, TAG) } applicationViewModel.errorMessageLiveData.observe(viewLifecycleOwner) { (requireActivity() as MainActivity).showSnackbarMessage(getString(it)) appPrivacyScoreLayout.setOnClickListener { ApplicationDialogFragment( R.drawable.ic_lock, getString(R.string.privacy_score), getString( R.string.privacy_description, PRIVACY_SCORE_SOURCE_CODE_URL, EXODUS_URL, PRIVACY_GUIDELINE_URL ) ).show(childFragmentManager, TAG) } } } private fun updateAppTitlePanel(it: FusedApp) { binding.titleInclude.apply { applicationIcon = appIcon appName.text = it.name appInfoFetchViewModel.getAuthorName(it).observe(viewLifecycleOwner) { appAuthor.text = it } categoryTitle.text = it.category if (origin == Origin.CLEANAPK) { appIcon.load(CleanAPKInterface.ASSET_URL + it.icon_image_path) } else { appIcon.load(it.icon_image_path) } } } private fun setupScreenshotRVAdapter() { screenshotsRVAdapter = ApplicationScreenshotsRVAdapter(origin) binding.recyclerView.apply { adapter = screenshotsRVAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) } } private fun setupToolbar(view: View) { val startDestination = findNavController().graph.startDestination if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() view.findNavController().navigate(action) } } else { binding.toolbar.setNavigationOnClickListener { view.findNavController().navigateUp() } } } Loading Loading @@ -381,7 +418,12 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { downloadPB, appSize ) Status.UNAVAILABLE -> handleUnavaiable(installButton, fusedApp, downloadPB, appSize) Status.UNAVAILABLE -> handleUnavaiable( installButton, fusedApp, downloadPB, appSize ) Status.QUEUED, Status.AWAITING, Status.DOWNLOADED -> handleQueued( installButton, fusedApp, Loading Loading @@ -669,7 +711,8 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } private fun updatePrivacyScore() { val privacyScore = privacyInfoViewModel.getPrivacyScore(applicationViewModel.fusedApp.value?.first) val privacyScore = privacyInfoViewModel.getPrivacyScore(applicationViewModel.fusedApp.value?.first) if (privacyScore != -1) { val appPrivacyScore = binding.ratingsInclude.appPrivacyScore appPrivacyScore.text = getString( Loading
app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +10 −7 Original line number Diff line number Diff line Loading @@ -78,13 +78,7 @@ class ApplicationListFragment : super.onViewCreated(view, savedInstanceState) _binding = FragmentApplicationListBinding.bind(view) binding.toolbarTitleTV.text = args.translation binding.toolbar.apply { setNavigationOnClickListener { view.findNavController().navigate(R.id.categoriesFragment) } } updateToolbar(view) setupRecyclerView(view) observeAppListLiveData() Loading @@ -100,6 +94,15 @@ class ApplicationListFragment : } } private fun updateToolbar(view: View) { binding.toolbarTitleTV.text = args.translation binding.toolbar.apply { setNavigationOnClickListener { view.findNavController().navigate(R.id.categoriesFragment) } } } private fun setupRecyclerView(view: View) { val recyclerView = initRecyclerView() findNavController().currentDestination?.id?.let { Loading
app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +91 −60 Original line number Diff line number Diff line Loading @@ -123,79 +123,110 @@ class ApplicationListRVAdapter( hidePrivacyScore() } applicationList.setOnClickListener { val action = when (currentDestinationId) { R.id.applicationListFragment -> { ApplicationListFragmentDirections.actionApplicationListFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) handleAppItemClick(searchApp, view) } R.id.searchFragment -> { SearchFragmentDirections.actionSearchFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) updateAppInfo(searchApp) updateRating(searchApp) updatePrivacyScore(searchApp, view) updateSourceTag(searchApp) setAppIcon(searchApp, shimmerDrawable) removeIsPurchasedObserver(holder) if (appInfoFetchViewModel.isAppInBlockedList(searchApp)) { setupShowMoreButton() } else { mainActivityViewModel.verifyUiFilter(searchApp) { setupInstallButton(searchApp, view, holder) } R.id.updatesFragment -> { UpdatesFragmentDirections.actionUpdatesFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } else -> null showCalculatedPrivacyScoreData(searchApp, view) } } private fun ApplicationListItemBinding.setAppIcon( searchApp: FusedApp, shimmerDrawable: ShimmerDrawable ) { when (searchApp.origin) { Origin.GPLAY -> { appIcon.load(searchApp.icon_image_path) { placeholder(shimmerDrawable) } action?.let { direction -> view.findNavController().navigate(direction) } } Origin.CLEANAPK -> { appIcon.load(CleanAPKInterface.ASSET_URL + searchApp.icon_image_path) { placeholder(shimmerDrawable) } } else -> Log.wtf(TAG, "${searchApp.package_name} is from an unknown origin") } } private fun ApplicationListItemBinding.updateAppInfo(searchApp: FusedApp) { appTitle.text = searchApp.name appInfoFetchViewModel.getAuthorName(searchApp).observe(lifecycleOwner!!) { appAuthor.text = it } } private fun ApplicationListItemBinding.updateRating(searchApp: FusedApp) { if (searchApp.ratings.usageQualityScore != -1.0) { appRating.text = searchApp.ratings.usageQualityScore.toString() appRatingBar.rating = searchApp.ratings.usageQualityScore.toFloat() } } private fun ApplicationListItemBinding.updatePrivacyScore( searchApp: FusedApp, view: View ) { if (searchApp.ratings.privacyScore != -1.0) { appPrivacyScore.text = view.context.getString( R.string.privacy_rating_out_of, searchApp.ratings.privacyScore.toInt().toString() ) } } private fun ApplicationListItemBinding.updateSourceTag(searchApp: FusedApp) { if (searchApp.source.isEmpty()) { sourceTag.visibility = View.INVISIBLE } else { sourceTag.visibility = View.VISIBLE } sourceTag.text = searchApp.source when (searchApp.origin) { Origin.GPLAY -> { appIcon.load(searchApp.icon_image_path) { placeholder(shimmerDrawable) } } Origin.CLEANAPK -> { appIcon.load(CleanAPKInterface.ASSET_URL + searchApp.icon_image_path) { placeholder(shimmerDrawable) } } else -> Log.wtf(TAG, "${searchApp.package_name} is from an unknown origin") } removeIsPurchasedObserver(holder) if (appInfoFetchViewModel.isAppInBlockedList(searchApp)) { setupShowMoreButton() } else { mainActivityViewModel.verifyUiFilter(searchApp) { setupInstallButton(searchApp, view, holder) private fun handleAppItemClick( searchApp: FusedApp, view: View ) { val action = when (currentDestinationId) { R.id.applicationListFragment -> { ApplicationListFragmentDirections.actionApplicationListFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } R.id.searchFragment -> { SearchFragmentDirections.actionSearchFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } showCalculatedPrivacyScoreData(searchApp, view) R.id.updatesFragment -> { UpdatesFragmentDirections.actionUpdatesFragmentToApplicationFragment( searchApp._id, searchApp.package_name, searchApp.origin ) } else -> null } action?.let { direction -> view.findNavController().navigate(direction) } } private fun removeIsPurchasedObserver(holder: ViewHolder) { Loading
app/src/main/java/foundation/e/apps/categories/model/CategoriesRVAdapter.kt +21 −13 Original line number Diff line number Diff line Loading @@ -59,12 +59,21 @@ class CategoriesRVAdapter : ) holder.itemView.findNavController().navigate(direction) } loadCategoryIcon(position) categoryTitle.text = oldList[position].title updateTag(position) } } private fun CategoriesListItemBinding.loadCategoryIcon(position: Int) { if (oldList[position].drawable != -1) { categoryIcon.load(oldList[position].drawable) } else { categoryIcon.load(oldList[position].imageUrl) } categoryTitle.text = oldList[position].title } private fun CategoriesListItemBinding.updateTag(position: Int) { val tag = oldList[position].tag if (tag.displayTag.isNotBlank()) { categoryTag.visibility = View.VISIBLE Loading @@ -74,7 +83,6 @@ class CategoriesRVAdapter : categoryTag.text = "" } } } override fun getItemCount(): Int { return oldList.size Loading