mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-06-15 19:20:53 -04:00
DO NOT MERGE: WIP: Migrate AppDetailsActivity to fragment
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -92,43 +92,6 @@
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name=".view.ui.details.EmptyAppDetailsActivity"
|
||||
android:noHistory="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="market" android:host="details" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="market.android.com" />
|
||||
<data android:path="/store/apps/details" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="play.google.com" />
|
||||
<data android:path="/store/apps/details" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".view.ui.details.AppDetailsActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".view.ui.details.DevProfileActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
||||
@@ -37,13 +37,13 @@ import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.MainActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.view.ui.details.AppDetailsActivity
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
val Context.inflater: LayoutInflater
|
||||
@@ -58,12 +58,11 @@ fun Context.browse(url: String, showOpenInAuroraAction: Boolean = false) {
|
||||
if (showOpenInAuroraAction) {
|
||||
val icon =
|
||||
ContextCompat.getDrawable(this, R.drawable.ic_open_in_new)?.toBitmap()
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, AppDetailsActivity::class.java),
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
val pendingIntent = NavDeepLinkBuilder(this)
|
||||
.setGraph(R.navigation.mobile_navigation)
|
||||
.setDestination(R.id.appDetailsFragment)
|
||||
.setComponentName(MainActivity::class.java)
|
||||
.createPendingIntent()
|
||||
customTabsIntent.setActionButton(
|
||||
icon!!,
|
||||
this.getString(R.string.open_in_aurora),
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.util.ArrayMap
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.getStyledAttributeColor
|
||||
@@ -46,7 +47,6 @@ import com.aurora.store.data.receiver.DownloadResumeReceiver
|
||||
import com.aurora.store.data.receiver.InstallReceiver
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.view.ui.details.AppDetailsActivity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.tonyodev.fetch2.*
|
||||
@@ -313,17 +313,12 @@ class NotificationService : Service() {
|
||||
}
|
||||
|
||||
private fun getContentIntentForDetails(app: App?): PendingIntent {
|
||||
val intent = Intent(this, AppDetailsActivity::class.java)
|
||||
intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app))
|
||||
val flags = if (isMAndAbove())
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
else PendingIntent.FLAG_CANCEL_CURRENT
|
||||
return PendingIntent.getActivity(
|
||||
this,
|
||||
packageName.hashCode(),
|
||||
intent,
|
||||
flags
|
||||
)
|
||||
return NavDeepLinkBuilder(this)
|
||||
.setGraph(R.navigation.mobile_navigation)
|
||||
.setDestination(R.id.appDetailsFragment)
|
||||
.setComponentName(MainActivity::class.java)
|
||||
.setArguments(bundleOf("packageName" to app!!.packageName))
|
||||
.createPendingIntent()
|
||||
}
|
||||
|
||||
private fun getContentIntentForDownloads(): PendingIntent {
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.view.ui.details.AppDetailsActivity
|
||||
import com.aurora.store.view.ui.details.DetailsExodusActivity
|
||||
import com.aurora.store.view.ui.details.DevAppsActivity
|
||||
import com.google.gson.Gson
|
||||
@@ -36,17 +35,6 @@ import java.lang.reflect.Modifier
|
||||
object NavigationUtil {
|
||||
val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create()
|
||||
|
||||
fun openDetailsActivity(context: Context, app: App) {
|
||||
val intent = Intent(
|
||||
context,
|
||||
AppDetailsActivity::class.java
|
||||
).apply {
|
||||
putExtra(Constants.STRING_EXTRA, gson.toJson(app))
|
||||
}
|
||||
val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity)
|
||||
context.startActivity(intent, options.toBundle())
|
||||
}
|
||||
|
||||
fun openDevAppsActivity(context: Context, app: App) {
|
||||
val intent = Intent(
|
||||
context,
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.aurora.Constants
|
||||
@@ -38,10 +37,7 @@ import com.aurora.store.view.ui.sheets.NetworkDialogSheet
|
||||
import com.aurora.store.view.ui.sheets.TOSSheet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity(), NetworkProvider.NetworkListener {
|
||||
@@ -56,7 +52,7 @@ abstract class BaseActivity : AppCompatActivity(), NetworkProvider.NetworkListen
|
||||
}
|
||||
|
||||
fun openDetailsActivity(app: App) {
|
||||
val intent = Intent(this, AppDetailsActivity::class.java)
|
||||
val intent = Intent(this, AppDetailsFragment::class.java)
|
||||
intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app))
|
||||
val options = ActivityOptions.makeSceneTransitionAnimation(this)
|
||||
startActivity(intent, options.toBundle())
|
||||
|
||||
@@ -23,11 +23,12 @@ import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.getEmptyActivityBundle
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Category
|
||||
import com.aurora.store.view.ui.details.AppDetailsActivity
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.aurora.store.view.ui.details.DevProfileActivity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
@@ -44,10 +45,9 @@ open class BaseFragment : Fragment {
|
||||
).create()
|
||||
|
||||
fun openDetailsActivity(app: App) {
|
||||
val intent = Intent(context, AppDetailsActivity::class.java)
|
||||
intent.putExtra(Constants.STRING_EXTRA, Gson().toJson(app))
|
||||
val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity())
|
||||
startActivity(intent, options.toBundle())
|
||||
findNavController().navigate(
|
||||
MobileNavigationDirections.actionGlobalAppDetailsFragment(app.packageName)
|
||||
)
|
||||
}
|
||||
|
||||
fun openCategoryBrowseActivity(category: Category) {
|
||||
|
||||
@@ -1,846 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
* Copyright (C) 2022, The Calyx Institute
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.*
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.store.MainActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.getGroupId
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.data.service.AppMetadataStatusListener
|
||||
import com.aurora.store.data.service.UpdateService
|
||||
import com.aurora.store.databinding.ActivityDetailsBinding
|
||||
import com.aurora.store.util.*
|
||||
import com.aurora.store.view.ui.sheets.InstallErrorDialogSheet
|
||||
import com.aurora.store.view.ui.sheets.ManualDownloadSheet
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import com.tonyodev.fetch2.*
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.io.File
|
||||
|
||||
class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
private lateinit var B: ActivityDetailsBinding
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>
|
||||
|
||||
private val startForStorageManagerResult =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (isRAndAbove() && Environment.isExternalStorageManager()) {
|
||||
updateApp(app)
|
||||
} else {
|
||||
toast(R.string.permissions_denied)
|
||||
}
|
||||
}
|
||||
private val startForPermissions =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
if (it) updateApp(app) else toast(R.string.permissions_denied)
|
||||
}
|
||||
|
||||
private lateinit var authData: AuthData
|
||||
private lateinit var app: App
|
||||
private var fetch: Fetch? = null
|
||||
private var downloadManager: DownloadManager? = null
|
||||
|
||||
private var attachToServiceCalled = false
|
||||
private var updateService: UpdateService? = null
|
||||
private var pendingAddListeners = true
|
||||
private var serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
||||
if (::fetchGroupListener.isInitialized && ::appMetadataListener.isInitialized && pendingAddListeners) {
|
||||
updateService!!.registerFetchListener(fetchGroupListener)
|
||||
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||
pendingAddListeners = false
|
||||
}
|
||||
if (listOfActionsWhenServiceAttaches.isNotEmpty()) {
|
||||
val iterator = listOfActionsWhenServiceAttaches.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
next.run()
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
}
|
||||
}
|
||||
private lateinit var fetchGroupListener: FetchGroupListener
|
||||
private lateinit var appMetadataListener: AppMetadataStatusListener
|
||||
private lateinit var completionMarker: java.io.File
|
||||
private lateinit var inProgressMarker: java.io.File
|
||||
|
||||
private var isExternal = false
|
||||
private var isNone = false
|
||||
private var status = Status.NONE
|
||||
private var isInstalled: Boolean = false
|
||||
private var isUpdatable: Boolean = false
|
||||
private var autoDownload: Boolean = false
|
||||
private var downloadOnly: Boolean = false
|
||||
|
||||
override fun onConnected() {
|
||||
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
|
||||
}
|
||||
|
||||
override fun onReconnected() {
|
||||
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
if (autoDownload) {
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: Any) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
}
|
||||
}
|
||||
is BusEvent.UninstallEvent -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
}
|
||||
}
|
||||
is BusEvent.ManualDownload -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
app.versionCode = event.versionCode
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
is InstallerEvent.Failed -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
InstallErrorDialogSheet.newInstance(
|
||||
app,
|
||||
event.packageName,
|
||||
event.error,
|
||||
event.extra
|
||||
).show(supportFragmentManager, "SED")
|
||||
attachActions()
|
||||
updateActionState(State.IDLE)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
B = ActivityDetailsBinding.inflate(layoutInflater)
|
||||
setContentView(B.root)
|
||||
|
||||
onNewIntent(intent)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this) {
|
||||
if (isExternal) {
|
||||
open(MainActivity::class.java, true)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
getUpdateServiceInstance()
|
||||
checkAndSetupInstall()
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
if (intent.scheme != null && (intent.scheme == "market" || intent.scheme == "http" || intent.scheme == "https")) {
|
||||
val packageName = intent.data!!.getQueryParameter("id")
|
||||
val packageVersion = intent.data!!.getQueryParameter("v")
|
||||
if (packageName.isNullOrEmpty()) {
|
||||
finishAfterTransition()
|
||||
} else {
|
||||
isExternal = true
|
||||
app = App(packageName)
|
||||
if (!packageVersion.isNullOrEmpty()) {
|
||||
app.versionCode = packageVersion.toInt()
|
||||
}
|
||||
fetchCompleteApp()
|
||||
}
|
||||
} else {
|
||||
val rawApp: String? = intent.getStringExtra(Constants.STRING_EXTRA)
|
||||
if (rawApp != null) {
|
||||
app = gson.fromJson(rawApp, App::class.java)
|
||||
isInstalled = PackageUtil.isInstalled(this, app.packageName)
|
||||
|
||||
inflatePartialApp()
|
||||
fetchCompleteApp()
|
||||
} else {
|
||||
finishAfterTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var uninstallActionEnabled = false
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_details, menu)
|
||||
if (::app.isInitialized) {
|
||||
val installed = PackageUtil.isInstalled(this, app.packageName)
|
||||
menu?.findItem(R.id.action_uninstall)?.isVisible = installed
|
||||
uninstallActionEnabled = installed
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
R.id.action_share -> {
|
||||
share(app)
|
||||
return true
|
||||
}
|
||||
R.id.action_uninstall -> {
|
||||
uninstallApp()
|
||||
return true
|
||||
}
|
||||
R.id.menu_download_manual -> {
|
||||
val sheet = ManualDownloadSheet.newInstance(app)
|
||||
sheet.isCancelable = false
|
||||
sheet.show(supportFragmentManager, ManualDownloadSheet.TAG)
|
||||
return true
|
||||
}
|
||||
R.id.action_playstore -> {
|
||||
browse("${Constants.SHARE_URL}${app.packageName}")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun attachToolbar() {
|
||||
setSupportActionBar(B.layoutDetailsToolbar.toolbar)
|
||||
val actionBar = supportActionBar
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayShowCustomEnabled(true)
|
||||
actionBar.setDisplayHomeAsUpEnabled(true)
|
||||
actionBar.elevation = 0f
|
||||
actionBar.title = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachActions() {
|
||||
flip(0)
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
private fun updateActionState(state: State) {
|
||||
B.layoutDetailsInstall.btnDownload.updateState(state)
|
||||
}
|
||||
|
||||
private fun openApp() {
|
||||
val intent = PackageUtil.getLaunchIntent(this, app.packageName)
|
||||
if (intent != null) {
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
toast("Unable to open app")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyAndInstall(files: List<Download>) {
|
||||
if (downloadOnly)
|
||||
return
|
||||
|
||||
var filesExist = true
|
||||
|
||||
files.forEach { download ->
|
||||
filesExist = filesExist && File(download.file).exists()
|
||||
}
|
||||
|
||||
if (filesExist)
|
||||
install(files)
|
||||
else
|
||||
purchase()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun install(files: List<Download>) {
|
||||
updateActionState(State.IDLE)
|
||||
|
||||
val apkFiles = files.filter { it.file.endsWith(".apk") }
|
||||
val preferredInstaller = Preferences.getInteger(this, Preferences.PREFERENCE_INSTALLER_ID)
|
||||
|
||||
if (apkFiles.size > 1 && preferredInstaller == 1) {
|
||||
showDialog(R.string.title_installer, R.string.dialog_desc_native_split)
|
||||
} else {
|
||||
task {
|
||||
AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
app.packageName,
|
||||
apkFiles.map { it.file }
|
||||
)
|
||||
} fail {
|
||||
Log.e(it.stackTraceToString())
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun uninstallApp() {
|
||||
task {
|
||||
AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
.uninstall(app.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachWhiteListStatus() {
|
||||
|
||||
}
|
||||
|
||||
private fun fetchCompleteApp() {
|
||||
task {
|
||||
authData = AuthProvider.with(this).getAuthData()
|
||||
return@task AppDetailsHelper(authData)
|
||||
.using(HttpClient.getPreferredClient())
|
||||
.getAppByPackageName(app.packageName)
|
||||
} successUi {
|
||||
if (isExternal) {
|
||||
app = it
|
||||
inflatePartialApp()
|
||||
}
|
||||
inflateExtraDetails(it)
|
||||
} failUi {
|
||||
toast("Failed to fetch app details")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflatePartialApp() {
|
||||
if (::app.isInitialized) {
|
||||
attachWhiteListStatus()
|
||||
attachHeader()
|
||||
attachToolbar()
|
||||
attachBottomSheet()
|
||||
attachFetch()
|
||||
attachActions()
|
||||
|
||||
if (autoDownload) {
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachHeader() {
|
||||
B.layoutDetailsApp.apply {
|
||||
imgIcon.load(app.iconArtwork.url) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transform(RoundedCorners(32))
|
||||
}
|
||||
|
||||
txtLine1.text = app.displayName
|
||||
txtLine2.text = app.developerName
|
||||
txtLine2.setOnClickListener {
|
||||
NavigationUtil.openDevAppsActivity(
|
||||
this@AppDetailsActivity,
|
||||
app
|
||||
)
|
||||
}
|
||||
txtLine3.text = ("${app.versionName} (${app.versionCode})")
|
||||
packageName.text = app.packageName
|
||||
|
||||
val tags = mutableListOf<String>()
|
||||
if (app.isFree)
|
||||
tags.add(getString(R.string.details_free))
|
||||
else
|
||||
tags.add(getString(R.string.details_paid))
|
||||
|
||||
if (app.containsAds)
|
||||
tags.add(getString(R.string.details_contains_ads))
|
||||
else
|
||||
tags.add(getString(R.string.details_no_ads))
|
||||
|
||||
txtLine4.text = tags.joinToString(separator = " • ")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateExtraDetails(app: App?) {
|
||||
app?.let {
|
||||
B.viewFlipper.displayedChild = 1
|
||||
inflateAppDescription(B.layoutDetailDescription, app)
|
||||
inflateAppRatingAndReviews(B.layoutDetailsReview, app)
|
||||
inflateAppDevInfo(B.layoutDetailsDev, app)
|
||||
inflateAppPrivacy(B.layoutDetailsPrivacy, app)
|
||||
inflateAppPermission(B.layoutDetailsPermissions, app)
|
||||
|
||||
if (!authData.isAnonymous) {
|
||||
app.testingProgram?.let {
|
||||
if (it.isAvailable && it.isSubscribed) {
|
||||
B.layoutDetailsApp.txtLine1.text = it.displayName
|
||||
}
|
||||
}
|
||||
|
||||
inflateBetaSubscription(B.layoutDetailsBeta, app)
|
||||
}
|
||||
|
||||
if (Preferences.getBoolean(this, Preferences.PREFERENCE_SIMILAR)) {
|
||||
inflateAppStream(B.epoxyRecyclerStream, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun startDownload() {
|
||||
when (status) {
|
||||
Status.PAUSED -> {
|
||||
fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity))
|
||||
}
|
||||
Status.DOWNLOADING -> {
|
||||
flip(1)
|
||||
toast("Already downloading")
|
||||
}
|
||||
Status.COMPLETED -> {
|
||||
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
|
||||
verifyAndInstall(it.downloads)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
|
||||
|
||||
private fun purchase() {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
updateActionState(State.PROGRESS)
|
||||
|
||||
if (PathUtil.needsStorageManagerPerm(app.fileList) || this.isExternalStorageEnable()) {
|
||||
if (isRAndAbove()) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
startForStorageManagerResult.launch(
|
||||
Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
)
|
||||
} else {
|
||||
updateApp(app)
|
||||
}
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
updateApp(app)
|
||||
} else {
|
||||
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateApp(app)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateApp(app: App) {
|
||||
if (updateService == null) {
|
||||
listOfActionsWhenServiceAttaches.add {
|
||||
updateService?.updateApp(app, true)
|
||||
}
|
||||
getUpdateServiceInstance()
|
||||
} else {
|
||||
updateService?.updateApp(app, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(
|
||||
fetchGroup: FetchGroup,
|
||||
etaInMilliSeconds: Long,
|
||||
downloadedBytesPerSecond: Long
|
||||
) {
|
||||
runOnUiThread {
|
||||
val progress = if (fetchGroup.groupDownloadProgress > 0)
|
||||
fetchGroup.groupDownloadProgress
|
||||
else
|
||||
0
|
||||
|
||||
if (progress == 100) {
|
||||
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||
return@runOnUiThread
|
||||
}
|
||||
B.layoutDetailsInstall.apply {
|
||||
txtProgressPercent.text = ("${progress}%")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
progressDownload.setProgress(progress, true)
|
||||
} else {
|
||||
progressDownload.progress = progress
|
||||
}
|
||||
|
||||
txtEta.text = CommonUtil.getETAString(
|
||||
this@AppDetailsActivity,
|
||||
etaInMilliSeconds
|
||||
)
|
||||
txtSpeed.text =
|
||||
CommonUtil.getDownloadSpeedString(
|
||||
this@AppDetailsActivity,
|
||||
downloadedBytesPerSecond
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun expandBottomSheet(message: String?) {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
with(B.layoutDetailsInstall) {
|
||||
txtPurchaseError.text = message
|
||||
btnDownload.updateState(State.IDLE)
|
||||
if (app.isFree)
|
||||
btnDownload.setText(R.string.action_install)
|
||||
else
|
||||
btnDownload.setText(app.price)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndSetupInstall() {
|
||||
isInstalled = PackageUtil.isInstalled(this, app.packageName)
|
||||
|
||||
B.layoutDetailsInstall.btnDownload.let { btn ->
|
||||
if (isInstalled) {
|
||||
isUpdatable = PackageUtil.isUpdatable(
|
||||
this,
|
||||
app.packageName,
|
||||
app.versionCode.toLong()
|
||||
)
|
||||
|
||||
val installedVersion = PackageUtil.getInstalledVersion(this, app.packageName)
|
||||
|
||||
if (isUpdatable) {
|
||||
B.layoutDetailsApp.txtLine3.text =
|
||||
("$installedVersion ➔ ${app.versionName} (${app.versionCode})")
|
||||
btn.setText(R.string.action_update)
|
||||
btn.addOnClickListener { startDownload() }
|
||||
} else {
|
||||
B.layoutDetailsApp.txtLine3.text = installedVersion
|
||||
btn.setText(R.string.action_open)
|
||||
btn.addOnClickListener { openApp() }
|
||||
}
|
||||
if (!uninstallActionEnabled) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
} else {
|
||||
if (app.isFree) {
|
||||
btn.setText(R.string.action_install)
|
||||
} else {
|
||||
btn.setText(app.price)
|
||||
}
|
||||
|
||||
btn.addOnClickListener {
|
||||
if (authData.isAnonymous && !app.isFree) {
|
||||
toast(R.string.toast_purchase_blocked)
|
||||
} else {
|
||||
btn.setText(R.string.download_metadata)
|
||||
startDownload()
|
||||
}
|
||||
}
|
||||
if (uninstallActionEnabled) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun flip(nextView: Int) {
|
||||
runOnUiThread {
|
||||
val displayChild = B.layoutDetailsInstall.viewFlipper.displayedChild
|
||||
if (displayChild != nextView) {
|
||||
B.layoutDetailsInstall.viewFlipper.displayedChild = nextView
|
||||
if (nextView == 0)
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachFetch() {
|
||||
if (fetch == null) {
|
||||
downloadManager = DownloadManager.with(this)
|
||||
fetch = downloadManager!!.fetch
|
||||
}
|
||||
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
|
||||
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
||||
status = Status.COMPLETED
|
||||
} else if (downloadManager?.isDownloading(fetchGroup) == true) {
|
||||
status = Status.DOWNLOADING
|
||||
flip(1)
|
||||
} else if (downloadManager?.isCanceled(fetchGroup) == true) {
|
||||
status = Status.CANCELLED
|
||||
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
||||
status = Status.PAUSED
|
||||
} else {
|
||||
status = Status.NONE
|
||||
}
|
||||
}
|
||||
|
||||
fetchGroupListener = object : AbstractFetchGroupListener() {
|
||||
|
||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStarted(
|
||||
groupId: Int,
|
||||
download: Download,
|
||||
downloadBlocks: List<DownloadBlock>,
|
||||
totalBlocks: Int,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(1)
|
||||
|
||||
val pkgDir = PathUtil.getPackageDirectory(applicationContext, app.packageName)
|
||||
completionMarker =
|
||||
java.io.File("$pkgDir/.${app.versionCode}.download-complete")
|
||||
inProgressMarker =
|
||||
java.io.File("$pkgDir/.${app.versionCode}.download-in-progress")
|
||||
|
||||
if (completionMarker.exists())
|
||||
completionMarker.delete()
|
||||
|
||||
inProgressMarker.createNewFile()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(1)
|
||||
inProgressMarker.parentFile?.mkdirs()
|
||||
inProgressMarker.createNewFile()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgress(
|
||||
groupId: Int,
|
||||
download: Download,
|
||||
etaInMilliSeconds: Long,
|
||||
downloadedBytesPerSecond: Long,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond)
|
||||
Log.i(
|
||||
"${app.displayName} : ${download.file} -> Progress : %d",
|
||||
fetchGroup.groupDownloadProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity) && fetchGroup.groupDownloadProgress == 100) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
updateProgress(fetchGroup, -1, -1)
|
||||
try {
|
||||
inProgressMarker.delete()
|
||||
completionMarker.createNewFile()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
inProgressMarker.delete()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(
|
||||
groupId: Int,
|
||||
download: Download,
|
||||
error: Error,
|
||||
throwable: Throwable?,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
inProgressMarker.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appMetadataListener = object : AppMetadataStatusListener {
|
||||
override fun onAppMetadataStatusError(reason: String, app: App) {
|
||||
if (app.packageName == this@AppDetailsActivity.app.packageName) {
|
||||
updateActionState(State.IDLE)
|
||||
expandBottomSheet(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getUpdateServiceInstance()
|
||||
|
||||
B.layoutDetailsInstall.imgCancel.setOnClickListener {
|
||||
fetch?.cancelGroup(
|
||||
app.getGroupId(this@AppDetailsActivity)
|
||||
)
|
||||
}
|
||||
if (updateService != null) {
|
||||
pendingAddListeners = false
|
||||
updateService!!.registerFetchListener(fetchGroupListener)
|
||||
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||
} else {
|
||||
pendingAddListeners = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getUpdateServiceInstance() {
|
||||
if (updateService == null && !attachToServiceCalled) {
|
||||
attachToServiceCalled = true
|
||||
val intent = Intent(this, UpdateService::class.java)
|
||||
startService(intent)
|
||||
bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachBottomSheet() {
|
||||
B.layoutDetailsInstall.apply {
|
||||
viewFlipper.setInAnimation(this@AppDetailsActivity, R.anim.fade_in)
|
||||
viewFlipper.setOutAnimation(this@AppDetailsActivity, R.anim.fade_out)
|
||||
}
|
||||
|
||||
bottomSheetBehavior = BottomSheetBehavior.from(B.layoutDetailsInstall.bottomSheet)
|
||||
bottomSheetBehavior.isDraggable = false
|
||||
|
||||
bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetCallback() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetBehavior.setDraggable(true)
|
||||
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetBehavior.isDraggable = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,443 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.*
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.ViewState
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.databinding.*
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.NavigationUtil
|
||||
import com.aurora.store.view.custom.RatingView
|
||||
import com.aurora.store.view.epoxy.controller.DetailsCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.epoxy.views.details.ReviewViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
import com.aurora.store.view.ui.sheets.PermissionBottomSheet
|
||||
import com.aurora.store.viewmodel.details.DetailsClusterViewModel
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
|
||||
|
||||
abstract class BaseDetailsActivity : BaseActivity() {
|
||||
|
||||
private val exodusBaseUrl = "https://reports.exodus-privacy.eu.org/api/search/"
|
||||
private val exodusApiKey = "Token bbe6ebae4ad45a9cbacb17d69739799b8df2c7ae"
|
||||
|
||||
//Sub Section Inflation
|
||||
fun inflateAppDescription(B: LayoutDetailsDescriptionBinding, app: App) {
|
||||
val installs = CommonUtil.addDiPrefix(app.installs)
|
||||
|
||||
if (installs != "NA") {
|
||||
B.txtInstalls.text = CommonUtil.addDiPrefix(app.installs)
|
||||
} else {
|
||||
B.txtInstalls.hide()
|
||||
}
|
||||
|
||||
B.txtSize.text = CommonUtil.addSiPrefix(app.size)
|
||||
B.txtRating.text = app.labeledRating
|
||||
B.txtSdk.text = ("Target SDK ${app.targetSdk}")
|
||||
B.txtUpdated.text = app.updatedOn
|
||||
B.txtDescription.text = HtmlCompat.fromHtml(
|
||||
app.shortDescription,
|
||||
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||
)
|
||||
|
||||
app.changes.apply {
|
||||
if (isEmpty()) {
|
||||
B.txtChangelog.text = getString(R.string.details_changelog_unavailable)
|
||||
} else {
|
||||
B.txtChangelog.text = HtmlCompat.fromHtml(
|
||||
this,
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
B.headerDescription.addClickListener {
|
||||
openDetailsMoreActivity(app)
|
||||
}
|
||||
|
||||
B.epoxyRecycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
var position = 0
|
||||
app.screenshots
|
||||
//.sortedWith { o1, o2 -> o2.height.compareTo(o1.height) }
|
||||
.forEach { artwork ->
|
||||
add(
|
||||
ScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.artwork(artwork)
|
||||
.position(position++)
|
||||
.callback(object : ScreenshotView.ScreenshotCallback {
|
||||
override fun onClick(position: Int) {
|
||||
openScreenshotActivity(app, position)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateAppRatingAndReviews(B: LayoutDetailsReviewBinding, app: App) {
|
||||
B.averageRating.text = app.rating.average.toString()
|
||||
B.txtReviewCount.text = app.rating.abbreviatedLabel
|
||||
|
||||
var totalStars = 0L
|
||||
totalStars += app.rating.oneStar
|
||||
totalStars += app.rating.twoStar
|
||||
totalStars += app.rating.threeStar
|
||||
totalStars += app.rating.fourStar
|
||||
totalStars += app.rating.fiveStar
|
||||
|
||||
B.avgRatingLayout.apply {
|
||||
removeAllViews()
|
||||
addView(addAvgReviews(5, totalStars, app.rating.fiveStar))
|
||||
addView(addAvgReviews(4, totalStars, app.rating.fourStar))
|
||||
addView(addAvgReviews(3, totalStars, app.rating.threeStar))
|
||||
addView(addAvgReviews(2, totalStars, app.rating.twoStar))
|
||||
addView(addAvgReviews(1, totalStars, app.rating.oneStar))
|
||||
}
|
||||
|
||||
B.averageRating.text = String.format(Locale.getDefault(), "%.1f", app.rating.average)
|
||||
B.txtReviewCount.text = app.rating.abbreviatedLabel
|
||||
|
||||
val authData = AuthProvider.with(this).getAuthData()
|
||||
|
||||
B.layoutUserReview.visibility = if (authData.isAnonymous) View.GONE else View.VISIBLE
|
||||
|
||||
B.btnPostReview.setOnClickListener {
|
||||
if (authData.isAnonymous) {
|
||||
toast(R.string.toast_anonymous_restriction)
|
||||
} else {
|
||||
addOrUpdateReview(B, app, Review().apply {
|
||||
title = authData.userProfile!!.name
|
||||
rating = B.userStars.rating.toInt()
|
||||
comment = B.inputReview.text.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
B.headerRatingReviews.addClickListener {
|
||||
openDetailsReviewActivity(app)
|
||||
}
|
||||
|
||||
task {
|
||||
fetchReviewSummary(app)
|
||||
} successUi {
|
||||
B.epoxyRecycler.withModels {
|
||||
it.take(4)
|
||||
.forEach {
|
||||
add(
|
||||
ReviewViewModel_()
|
||||
.id(it.timeStamp)
|
||||
.review(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
} failUi {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun inflateAppPrivacy(B: LayoutDetailsPrivacyBinding, app: App) {
|
||||
|
||||
task {
|
||||
fetchReport(app.packageName)
|
||||
} successUi { report ->
|
||||
if (report.trackers.isNotEmpty()) {
|
||||
B.txtStatus.apply {
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
this@BaseDetailsActivity,
|
||||
if (report.trackers.size > 4)
|
||||
R.color.colorRed
|
||||
else
|
||||
R.color.colorOrange
|
||||
)
|
||||
)
|
||||
text =
|
||||
("${report.trackers.size} ${getString(R.string.exodus_substring)} ${report.version}")
|
||||
}
|
||||
|
||||
B.headerPrivacy.addClickListener {
|
||||
NavigationUtil.openExodusActivity(this, app, report)
|
||||
}
|
||||
} else {
|
||||
B.txtStatus.apply {
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
this@BaseDetailsActivity,
|
||||
R.color.colorGreen
|
||||
)
|
||||
)
|
||||
text = getString(R.string.exodus_no_tracker)
|
||||
}
|
||||
}
|
||||
} failUi {
|
||||
B.txtStatus.text = it.message
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateAppDevInfo(B: LayoutDetailsDevBinding, app: App) {
|
||||
if (app.developerAddress.isNotEmpty()) {
|
||||
B.devAddress.apply {
|
||||
setTxtSubtitle(
|
||||
HtmlCompat.fromHtml(
|
||||
app.developerAddress,
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
).toString()
|
||||
)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
if (app.developerWebsite.isNotEmpty()) {
|
||||
B.devWeb.apply {
|
||||
setTxtSubtitle(app.developerWebsite)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
if (app.developerEmail.isNotEmpty()) {
|
||||
B.devMail.apply {
|
||||
setTxtSubtitle(app.developerEmail)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateBetaSubscription(B: LayoutDetailsBetaBinding, app: App) {
|
||||
app.testingProgram?.let { betaProgram ->
|
||||
if (betaProgram.isAvailable) {
|
||||
B.root.show()
|
||||
|
||||
updateBetaActions(B, betaProgram.isSubscribed)
|
||||
|
||||
if (betaProgram.isSubscribedAndInstalled) {
|
||||
|
||||
}
|
||||
|
||||
B.imgBeta.load(betaProgram.artwork.url) {
|
||||
|
||||
}
|
||||
|
||||
B.btnBetaAction.setOnClickListener {
|
||||
val authData = AuthProvider.with(this).getAuthData()
|
||||
task {
|
||||
B.btnBetaAction.text = getString(R.string.action_pending)
|
||||
B.btnBetaAction.isEnabled = false
|
||||
AppDetailsHelper(authData).testingProgram(
|
||||
app.packageName,
|
||||
!betaProgram.isSubscribed
|
||||
)
|
||||
} successUi {
|
||||
B.btnBetaAction.isEnabled = true
|
||||
if (it.subscribed) {
|
||||
updateBetaActions(B, true)
|
||||
}
|
||||
if (it.unsubscribed) {
|
||||
updateBetaActions(B, false)
|
||||
}
|
||||
} failUi {
|
||||
updateBetaActions(B, betaProgram.isSubscribed)
|
||||
toast(getString(R.string.details_beta_delay))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
B.root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateAppStream(epoxyRecyclerView: EpoxyRecyclerView, app: App) {
|
||||
app.detailsStreamUrl?.let {
|
||||
val VM = ViewModelProvider(this)[DetailsClusterViewModel::class.java]
|
||||
|
||||
val carouselController =
|
||||
DetailsCarouselController(object : GenericCarouselController.Callbacks {
|
||||
override fun onHeaderClicked(streamCluster: StreamCluster) {
|
||||
if (streamCluster.clusterBrowseUrl.isNotEmpty())
|
||||
openStreamBrowseActivity(
|
||||
streamCluster.clusterBrowseUrl,
|
||||
streamCluster.clusterTitle
|
||||
)
|
||||
else
|
||||
toast(getString(R.string.toast_page_unavailable))
|
||||
}
|
||||
|
||||
override fun onClusterScrolled(streamCluster: StreamCluster) {
|
||||
VM.observeCluster(streamCluster)
|
||||
}
|
||||
|
||||
override fun onAppClick(app: App) {
|
||||
openDetailsActivity(app)
|
||||
}
|
||||
|
||||
override fun onAppLongClick(app: App) {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
VM.liveData.observe(this) {
|
||||
when (it) {
|
||||
is ViewState.Empty -> {
|
||||
}
|
||||
is ViewState.Loading -> {
|
||||
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
|
||||
}
|
||||
is ViewState.Status -> {
|
||||
|
||||
}
|
||||
is ViewState.Success<*> -> {
|
||||
carouselController.setData(it.data as StreamBundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
epoxyRecyclerView.setController(carouselController)
|
||||
|
||||
VM.getStreamBundle(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateAppPermission(B: LayoutDetailsPermissionsBinding, app: App) {
|
||||
B.headerPermission.addClickListener {
|
||||
if (app.permissions.size > 0) {
|
||||
PermissionBottomSheet.newInstance(app)
|
||||
.show(supportFragmentManager, PermissionBottomSheet.TAG)
|
||||
}
|
||||
}
|
||||
B.txtPermissionCount.text = ("${app.permissions.size} permissions")
|
||||
}
|
||||
|
||||
private fun updateBetaActions(B: LayoutDetailsBetaBinding, isSubscribed: Boolean) {
|
||||
if (isSubscribed) {
|
||||
B.btnBetaAction.text = getString(R.string.action_leave)
|
||||
B.txtBetaTitle.text = getString(R.string.details_beta_subscribed)
|
||||
} else {
|
||||
B.btnBetaAction.text = getString(R.string.action_join)
|
||||
B.txtBetaTitle.text = getString(R.string.details_beta_available)
|
||||
}
|
||||
}
|
||||
|
||||
/* App Review Helpers */
|
||||
|
||||
private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout {
|
||||
return RatingView(this, number, max.toInt(), rating.toInt())
|
||||
}
|
||||
|
||||
private fun addOrUpdateReview(
|
||||
B: LayoutDetailsReviewBinding,
|
||||
app: App,
|
||||
review: Review,
|
||||
isBeta: Boolean = false
|
||||
) {
|
||||
task {
|
||||
val authData = AuthProvider.with(this).getAuthData()
|
||||
ReviewsHelper(authData)
|
||||
.using(HttpClient.getPreferredClient())
|
||||
.addOrEditReview(
|
||||
app.packageName,
|
||||
review.title,
|
||||
review.comment,
|
||||
review.rating,
|
||||
isBeta
|
||||
)
|
||||
}.successUi {
|
||||
it?.let {
|
||||
B.userStars.rating = it.rating.toFloat()
|
||||
Toast.makeText(this, getString(R.string.toast_rated_success), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}.failUi {
|
||||
Toast.makeText(this, getString(R.string.toast_rated_failed), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchReviewSummary(app: App): List<Review> {
|
||||
val authData = AuthProvider
|
||||
.with(this)
|
||||
.getAuthData()
|
||||
val reviewsHelper = ReviewsHelper(authData)
|
||||
.using(HttpClient.getPreferredClient())
|
||||
return reviewsHelper.getReviewSummary(app.packageName)
|
||||
}
|
||||
|
||||
/* App Privacy Helpers */
|
||||
|
||||
private fun parseResponse(response: String, packageName: String): List<Report> {
|
||||
try {
|
||||
val jsonObject = JSONObject(response)
|
||||
val exodusObject = jsonObject.getJSONObject(packageName)
|
||||
val exodusReport: ExodusReport = gson.fromJson(
|
||||
exodusObject.toString(),
|
||||
ExodusReport::class.java
|
||||
)
|
||||
return exodusReport.reports
|
||||
} catch (e: Exception) {
|
||||
throw Exception("No reports found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchReport(packageName: String): Report {
|
||||
val headers: MutableMap<String, String> = mutableMapOf()
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Authorization"] = exodusApiKey
|
||||
|
||||
val url = exodusBaseUrl + packageName
|
||||
|
||||
val playResponse = HttpClient
|
||||
.getPreferredClient()
|
||||
.get(url, headers)
|
||||
|
||||
if (playResponse.isSuccessful) {
|
||||
return parseResponse(String(playResponse.responseBytes), packageName)[0]
|
||||
} else {
|
||||
throw Exception("Failed to fetch report")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.aurora.store.R
|
||||
|
||||
class EmptyAppDetailsActivity: AppCompatActivity(R.layout.activity_details) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
onNewIntent(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
val validSchemes = listOf("market", "http", "https")
|
||||
|
||||
if (intent != null && validSchemes.any { it == intent.scheme }) {
|
||||
if (intent.data!!.getQueryParameter("id").isNullOrEmpty()) {
|
||||
finishAfterTransition()
|
||||
} else {
|
||||
// Construct a new intent manually to avoid accepting extras from external apps
|
||||
Intent(this, AppDetailsActivity::class.java).also { extIntent ->
|
||||
extIntent.data = intent.data
|
||||
startActivity(extIntent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.details.AppDetailsFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -120,4 +121,4 @@
|
||||
<include
|
||||
android:id="@+id/layout_details_install"
|
||||
layout="@layout/layout_details_install" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -98,4 +98,18 @@
|
||||
android:name="com.aurora.store.view.ui.downloads.DownloadFragment"
|
||||
android:label="@string/title_download_manager"
|
||||
tools:layout="@layout/fragment_download" />
|
||||
</navigation>
|
||||
<fragment
|
||||
android:id="@+id/appDetailsFragment"
|
||||
android:name="com.aurora.store.view.ui.details.AppDetailsFragment"
|
||||
tools:layout="@layout/fragment_details" >
|
||||
<argument
|
||||
android:name="packageName"
|
||||
app:argType="string" />
|
||||
<deepLink
|
||||
app:action="android.intent.action.VIEW"
|
||||
app:uri="play.google.com/store/apps/details?id={packageName}" />
|
||||
</fragment>
|
||||
<action
|
||||
android:id="@+id/action_global_appDetailsFragment"
|
||||
app:destination="@id/appDetailsFragment" />
|
||||
</navigation>
|
||||
|
||||
Reference in New Issue
Block a user