Improve installer & related UI
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.*
|
||||
|
||||
fun Long.toDate(): String {
|
||||
@@ -27,3 +29,11 @@ fun Long.toDate(): String {
|
||||
calendar.timeInMillis = this
|
||||
return DateFormat.format("dd/MM/yy", calendar).toString()
|
||||
}
|
||||
|
||||
fun Throwable.stackTraceToString(): String {
|
||||
val stringWriter = StringWriter(1024)
|
||||
val printWriter = PrintWriter(stringWriter)
|
||||
printStackTrace(printWriter)
|
||||
printWriter.close()
|
||||
return stringWriter.toString()
|
||||
}
|
||||
@@ -36,6 +36,10 @@ class AuroraApplication : MultiDexApplication() {
|
||||
private lateinit var fetch: Fetch
|
||||
private lateinit var packageManagerReceiver: PackageManagerReceiver
|
||||
|
||||
companion object{
|
||||
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
||||
@@ -28,4 +28,17 @@ sealed class BusEvent {
|
||||
var email: String = String(),
|
||||
var aasToken: String = String()
|
||||
) : BusEvent()
|
||||
}
|
||||
|
||||
sealed class SessionEvent {
|
||||
data class Success(
|
||||
var packageName: String? = "",
|
||||
var extra: String? = ""
|
||||
) : SessionEvent()
|
||||
|
||||
data class Failed(
|
||||
var packageName: String? = "",
|
||||
var error: String? = "",
|
||||
var extra: String? = ""
|
||||
) : SessionEvent()
|
||||
}
|
||||
@@ -25,9 +25,7 @@ import com.aurora.store.data.SingletonHolder
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
|
||||
open class AppInstaller private constructor(var context: Context) {
|
||||
|
||||
companion object : SingletonHolder<AppInstaller, Context>(::AppInstaller)
|
||||
open class AppInstaller constructor(var context: Context) {
|
||||
|
||||
fun getPreferredInstaller(): IInstaller {
|
||||
val prefValue = Preferences.getInteger(
|
||||
|
||||
@@ -24,24 +24,22 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.store.AuroraApplication
|
||||
import com.aurora.store.BuildConfig
|
||||
import java.io.File
|
||||
|
||||
private val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
|
||||
abstract class InstallerBase(protected var context: Context) : IInstaller {
|
||||
|
||||
|
||||
override fun clearQueue() {
|
||||
enqueuedInstalls.clear()
|
||||
AuroraApplication.enqueuedInstalls.clear()
|
||||
}
|
||||
|
||||
override fun isAlreadyQueued(packageName: String): Boolean {
|
||||
return enqueuedInstalls.contains(packageName)
|
||||
return AuroraApplication.enqueuedInstalls.contains(packageName)
|
||||
}
|
||||
|
||||
override fun removeFromInstallQueue(packageName: String) {
|
||||
enqueuedInstalls.remove(packageName)
|
||||
AuroraApplication.enqueuedInstalls.remove(packageName)
|
||||
}
|
||||
|
||||
override fun uninstall(packageName: String) {
|
||||
|
||||
@@ -25,52 +25,69 @@ import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
import com.aurora.store.util.Log
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class InstallerService : Service() {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -69)
|
||||
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||
|
||||
//Send broadcast for the installation status of the package
|
||||
sendStatusBroadcast(status, packageName)
|
||||
|
||||
//Launch user confirmation activity
|
||||
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
confirmationIntent!!.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
confirmationIntent.putExtra(
|
||||
Intent.EXTRA_INSTALLER_PACKAGE_NAME,
|
||||
"com.android.vending"
|
||||
)
|
||||
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
startActivity(confirmationIntent)
|
||||
} catch (e: Exception) {
|
||||
sendStatusBroadcast(PackageInstaller.STATUS_FAILURE, packageName)
|
||||
}
|
||||
val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> promptUser(intent)
|
||||
else -> postStatus(status, packageName, extra)
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun sendStatusBroadcast(status: Int, packageName: String?) {
|
||||
if (StringUtils.isNotEmpty(packageName)) {
|
||||
val statusIntent = Intent(ACTION_SESSION_INSTALLER)
|
||||
statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status)
|
||||
statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
|
||||
sendBroadcast(statusIntent)
|
||||
private fun promptUser(intent: Intent) {
|
||||
val confirmationIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
|
||||
confirmationIntent?.let {
|
||||
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
|
||||
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
try {
|
||||
startActivity(it)
|
||||
} catch (e: Exception) {
|
||||
Log.e(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun postStatus(status: Int, packageName: String?, extra: String?) {
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
EventBus.getDefault().post(SessionEvent.Success(packageName, "Success"))
|
||||
}
|
||||
else -> {
|
||||
val errorString = getErrorString(status)
|
||||
Log.e("$packageName : $errorString")
|
||||
EventBus.getDefault().post(SessionEvent.Failed(packageName, errorString, extra))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorString(status: Int): String {
|
||||
return when (status) {
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> getString(R.string.installer_status_user_action)
|
||||
PackageInstaller.STATUS_FAILURE_BLOCKED -> getString(R.string.installer_status_failure_blocked)
|
||||
PackageInstaller.STATUS_FAILURE_CONFLICT -> getString(R.string.installer_status_failure_conflict)
|
||||
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> getString(R.string.installer_status_failure_incompatible)
|
||||
PackageInstaller.STATUS_FAILURE_INVALID -> getString(R.string.installer_status_failure_invalid)
|
||||
PackageInstaller.STATUS_FAILURE_STORAGE -> getString(R.string.installer_status_failure_storage)
|
||||
else -> getString(R.string.installer_status_failure)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER"
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,13 @@
|
||||
package com.aurora.store.data.installer
|
||||
|
||||
import android.content.Context
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
import com.aurora.store.util.Log
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -50,7 +52,12 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
xInstallLegacy(packageName, it)
|
||||
}
|
||||
} else {
|
||||
context.toast(context.getString(R.string.installer_root_unavailable))
|
||||
val event = SessionEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_root_unavailable)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
||||
}
|
||||
}
|
||||
@@ -80,12 +87,34 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
.exec()
|
||||
}
|
||||
|
||||
Shell.su("pm install-commit $sessionId").exec()
|
||||
val shellResult = Shell.su("pm install-commit $sessionId").exec()
|
||||
|
||||
if (!shellResult.isSuccess) {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = SessionEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
parseError(shellResult)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = SessionEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_root_unavailable)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = SessionEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_status_failure_session)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +125,8 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseError(result: Shell.Result): String {
|
||||
return result.err.joinToString(separator = "\n")
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,10 @@ import androidx.core.content.FileProvider
|
||||
import com.aurora.services.IPrivilegedCallback
|
||||
import com.aurora.services.IPrivilegedService
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
import com.aurora.store.util.Log
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
@@ -65,27 +68,51 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
||||
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.installSplitPackage(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
Log.e("$packageName : $returnCode")
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.installSplitPackage(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
EventBus
|
||||
.getDefault()
|
||||
.post(
|
||||
SessionEvent.Failed(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
)
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
}
|
||||
} else {
|
||||
Log.e(context.getString(R.string.installer_service_unavailable))
|
||||
EventBus
|
||||
.getDefault()
|
||||
.post(
|
||||
SessionEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_unavailable)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,12 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.extensions.isNAndAbove
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
import com.aurora.store.util.Log
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
@@ -57,18 +60,22 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(packageName)
|
||||
if (isNAndAbove()) {
|
||||
setOriginatingUid(android.os.Process.myUid())
|
||||
}
|
||||
}
|
||||
val sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
|
||||
try {
|
||||
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
var apkId = 1
|
||||
|
||||
for (uri in uriList) {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val outputStream = session.openWrite(
|
||||
"${packageName}_${apkId++}",
|
||||
"${packageName}_${System.currentTimeMillis()}",
|
||||
0,
|
||||
-1
|
||||
)
|
||||
@@ -81,11 +88,11 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
IOUtils.close(outputStream)
|
||||
}
|
||||
|
||||
val intent = Intent(context, InstallerService::class.java)
|
||||
val callBackIntent = Intent(context, InstallerService::class.java)
|
||||
val pendingIntent = PendingIntent.getService(
|
||||
context,
|
||||
sessionId,
|
||||
intent,
|
||||
callBackIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
@@ -95,6 +102,12 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
} catch (e: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = SessionEvent.Failed(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
Log.e("Failed to install $packageName : %s", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
.post(InstallEvent(packageName, ""))
|
||||
|
||||
//Clear installation queue
|
||||
AppInstaller.with(context)
|
||||
AppInstaller(context)
|
||||
.getPreferredInstaller()
|
||||
.removeFromInstallQueue(packageName)
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ import android.util.ArrayMap
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.getStyledAttributeColor
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.receiver.DownloadCancelReceiver
|
||||
import com.aurora.store.data.receiver.DownloadPauseReceiver
|
||||
@@ -46,6 +48,8 @@ import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.tonyodev.fetch2.*
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
|
||||
@@ -83,6 +87,8 @@ class NotificationService : Service() {
|
||||
|
||||
Log.i("Notification Service Started")
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
//Create Notification Channels : General & Alert
|
||||
@@ -97,9 +103,6 @@ class NotificationService : Service() {
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
showNotification(groupId, download, fetchGroup)
|
||||
if (fetchGroup.groupDownloadProgress == 100) {
|
||||
install(download.tag!!, fetchGroup.downloads)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(
|
||||
@@ -214,8 +217,8 @@ class NotificationService : Service() {
|
||||
}
|
||||
val progress = fetchGroup.groupDownloadProgress
|
||||
val progressBigText = NotificationCompat.BigTextStyle()
|
||||
when (status) {
|
||||
|
||||
when (status) {
|
||||
Status.QUEUED -> {
|
||||
builder.setProgress(100, 0, true)
|
||||
progressBigText.bigText(getString(R.string.download_queued))
|
||||
@@ -308,7 +311,6 @@ class NotificationService : Service() {
|
||||
app.id,
|
||||
builder.build()
|
||||
)
|
||||
//}
|
||||
}
|
||||
|
||||
private fun getPauseIntent(groupId: Int): PendingIntent {
|
||||
@@ -356,9 +358,28 @@ class NotificationService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
@Subscribe()
|
||||
fun onEventMainThread(event: Any) {
|
||||
when (event) {
|
||||
is SessionEvent.Success -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.extra)
|
||||
}
|
||||
is SessionEvent.Failed -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.error)
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun install(packageName: String, files: List<Download>) {
|
||||
AppInstaller.with(this)
|
||||
AppInstaller(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
packageName,
|
||||
@@ -370,9 +391,25 @@ class NotificationService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
private fun notifyInstallationStatus(app: App, status: String?) {
|
||||
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_ALERT)
|
||||
builder.color = getStyledAttributeColor(R.attr.colorAccent)
|
||||
builder.setSmallIcon(R.drawable.ic_install)
|
||||
builder.setContentTitle(app.displayName)
|
||||
builder.setContentText(status)
|
||||
builder.setSubText(app.packageName)
|
||||
|
||||
notificationManager.notify(
|
||||
app.packageName,
|
||||
app.id,
|
||||
builder.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("Notification Service Stopped")
|
||||
fetch.removeListener(fetchListener)
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,22 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.custom
|
||||
package com.aurora.store.view.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewActionButtonBinding
|
||||
import nl.komponents.kovenant.task
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ActionButton : RelativeLayout {
|
||||
|
||||
@@ -64,23 +70,71 @@ class ActionButton : RelativeLayout {
|
||||
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton)
|
||||
val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText)
|
||||
|
||||
val btnTxtColor = typedArray.getResourceId(
|
||||
R.styleable.ActionButton_btnActionTextColor,
|
||||
R.color.colorWhite
|
||||
)
|
||||
|
||||
val stateIcon = typedArray.getResourceId(
|
||||
R.styleable.ActionButton_btnActionIcon,
|
||||
R.drawable.ic_check
|
||||
)
|
||||
|
||||
val stateColor = ContextCompat.getColor(context, btnTxtColor)
|
||||
|
||||
B.btn.text = btnTxt
|
||||
B.btn.setTextColor(stateColor)
|
||||
B.img.setImageDrawable(ContextCompat.getDrawable(context, stateIcon))
|
||||
if (isLAndAbove()) {
|
||||
B.img.imageTintList = ColorStateList.valueOf(stateColor)
|
||||
}
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
fun setText(text: String) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btn.text = text
|
||||
}
|
||||
|
||||
fun updateProgress(isVisible: Boolean) {
|
||||
if (isVisible)
|
||||
B.progress.visibility = View.VISIBLE
|
||||
else
|
||||
fun setText(text: Int) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btn.text = getString(text)
|
||||
}
|
||||
|
||||
B.progress.visibility = View.INVISIBLE
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.IDLE -> 0
|
||||
State.PROGRESS -> 1
|
||||
State.COMPLETE -> 2
|
||||
}
|
||||
|
||||
if (B.viewFlipper.displayedChild != displayChild) {
|
||||
runOnUiThread {
|
||||
B.viewFlipper.displayedChild = displayChild
|
||||
|
||||
if (displayChild == 2)
|
||||
switchToIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToIdle() {
|
||||
task {
|
||||
TimeUnit.SECONDS.sleep(3)
|
||||
} success {
|
||||
updateState(State.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnClickListener(onClickListener: OnClickListener) {
|
||||
B.btn.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.custom
|
||||
package com.aurora.store.view.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
@@ -0,0 +1,398 @@
|
||||
package com.aurora.store.view.custom.progress;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.aurora.store.R;
|
||||
import com.aurora.store.view.custom.progress.indicators.BallPulseIndicator;
|
||||
import com.aurora.store.view.custom.progress.indicators.Indicator;
|
||||
|
||||
|
||||
public class AuroraProgressView extends View {
|
||||
|
||||
private static final String TAG = "AuroraProgressView";
|
||||
|
||||
private static final BallPulseIndicator DEFAULT_INDICATOR = new BallPulseIndicator();
|
||||
|
||||
private static final int MIN_SHOW_TIME = 500; // ms
|
||||
private static final int MIN_DELAY = 500; // ms
|
||||
|
||||
private long startTime = -1;
|
||||
|
||||
private boolean postedHide = false;
|
||||
private boolean postedShow = false;
|
||||
private boolean dismissed = false;
|
||||
|
||||
private final Runnable mDelayedHide = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
postedHide = false;
|
||||
startTime = -1;
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mDelayedShow = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
postedShow = false;
|
||||
if (!dismissed) {
|
||||
startTime = System.currentTimeMillis();
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int minWidth;
|
||||
int maxWidth;
|
||||
int minHeight;
|
||||
int maxHeight;
|
||||
|
||||
private Indicator indicator;
|
||||
private int indicatorColor;
|
||||
|
||||
private boolean mShouldStartAnimationDrawable;
|
||||
|
||||
public AuroraProgressView(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public AuroraProgressView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
public AuroraProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AuroraProgressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(context, attrs, defStyleAttr, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
minWidth = 24;
|
||||
maxWidth = 48;
|
||||
minHeight = 24;
|
||||
maxHeight = 48;
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.AuroraProgressView, defStyleAttr, defStyleRes);
|
||||
|
||||
minWidth = a.getDimensionPixelSize(R.styleable.AuroraProgressView_minWidth, minWidth);
|
||||
maxWidth = a.getDimensionPixelSize(R.styleable.AuroraProgressView_maxWidth, maxWidth);
|
||||
minHeight = a.getDimensionPixelSize(R.styleable.AuroraProgressView_minHeight, minHeight);
|
||||
maxHeight = a.getDimensionPixelSize(R.styleable.AuroraProgressView_maxHeight, maxHeight);
|
||||
String indicatorName = a.getString(R.styleable.AuroraProgressView_indicatorName);
|
||||
indicatorColor = a.getColor(R.styleable.AuroraProgressView_indicatorColor, Color.WHITE);
|
||||
setIndicator(indicatorName);
|
||||
|
||||
if (indicator == null) {
|
||||
setIndicator(DEFAULT_INDICATOR);
|
||||
}
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public Indicator getIndicator() {
|
||||
return indicator;
|
||||
}
|
||||
|
||||
public void setIndicator(Indicator d) {
|
||||
if (indicator != d) {
|
||||
if (indicator != null) {
|
||||
indicator.setCallback(null);
|
||||
unscheduleDrawable(indicator);
|
||||
}
|
||||
|
||||
indicator = d;
|
||||
//need to set indicator color again if you didn't specified when you update the indicator .
|
||||
setIndicatorColor(indicatorColor);
|
||||
if (d != null) {
|
||||
d.setCallback(this);
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setIndicatorColor(int color) {
|
||||
this.indicatorColor = color;
|
||||
indicator.setColor(color);
|
||||
}
|
||||
|
||||
public void setIndicator(String indicatorName) {
|
||||
if (TextUtils.isEmpty(indicatorName)) {
|
||||
return;
|
||||
}
|
||||
StringBuilder drawableClassName = new StringBuilder();
|
||||
if (!indicatorName.contains(".")) {
|
||||
String defaultPackageName = getClass().getPackage().getName();
|
||||
drawableClassName.append(defaultPackageName)
|
||||
.append(".indicators")
|
||||
.append(".");
|
||||
}
|
||||
drawableClassName.append(indicatorName);
|
||||
try {
|
||||
Class<?> drawableClass = Class.forName(drawableClassName.toString());
|
||||
Indicator indicator = (Indicator) drawableClass.newInstance();
|
||||
setIndicator(indicator);
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG, "Didn't find your class , check the name again !");
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void smoothToShow() {
|
||||
startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_in));
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
public void smoothToHide() {
|
||||
startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));
|
||||
setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
dismissed = true;
|
||||
removeCallbacks(mDelayedShow);
|
||||
long diff = System.currentTimeMillis() - startTime;
|
||||
if (diff >= MIN_SHOW_TIME || startTime == -1) {
|
||||
// The progress spinner has been shown long enough
|
||||
// OR was not shown yet. If it wasn't shown yet,
|
||||
// it will just never be shown.
|
||||
setVisibility(View.GONE);
|
||||
} else {
|
||||
// The progress spinner is shown, but not long enough,
|
||||
// so put a delayed message in to hide it when its been
|
||||
// shown long enough.
|
||||
if (!postedHide) {
|
||||
postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
|
||||
postedHide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void show() {
|
||||
// Reset the start time.
|
||||
startTime = -1;
|
||||
dismissed = false;
|
||||
removeCallbacks(mDelayedHide);
|
||||
if (!postedShow) {
|
||||
postDelayed(mDelayedShow, MIN_DELAY);
|
||||
postedShow = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(Drawable who) {
|
||||
return who == indicator
|
||||
|| super.verifyDrawable(who);
|
||||
}
|
||||
|
||||
void startAnimation() {
|
||||
if (getVisibility() != VISIBLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indicator != null) {
|
||||
mShouldStartAnimationDrawable = true;
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
void stopAnimation() {
|
||||
if (indicator != null) {
|
||||
indicator.stop();
|
||||
mShouldStartAnimationDrawable = false;
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int v) {
|
||||
if (getVisibility() != v) {
|
||||
super.setVisibility(v);
|
||||
if (v == GONE || v == INVISIBLE) {
|
||||
stopAnimation();
|
||||
} else {
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (visibility == GONE || visibility == INVISIBLE) {
|
||||
stopAnimation();
|
||||
} else {
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable dr) {
|
||||
if (verifyDrawable(dr)) {
|
||||
final Rect dirty = dr.getBounds();
|
||||
final int scrollX = getScrollX() + getPaddingLeft();
|
||||
final int scrollY = getScrollY() + getPaddingTop();
|
||||
|
||||
invalidate(dirty.left + scrollX, dirty.top + scrollY,
|
||||
dirty.right + scrollX, dirty.bottom + scrollY);
|
||||
} else {
|
||||
super.invalidateDrawable(dr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
updateDrawableBounds(w, h);
|
||||
}
|
||||
|
||||
private void updateDrawableBounds(int w, int h) {
|
||||
// onDraw will translate the canvas so we draw starting at 0,0.
|
||||
// Subtract out padding for the purposes of the calculations below.
|
||||
w -= getPaddingRight() + getPaddingLeft();
|
||||
h -= getPaddingTop() + getPaddingBottom();
|
||||
|
||||
int right = w;
|
||||
int bottom = h;
|
||||
int top = 0;
|
||||
int left = 0;
|
||||
|
||||
if (indicator != null) {
|
||||
// Maintain aspect ratio. Certain kinds of animated drawables
|
||||
// get very confused otherwise.
|
||||
final int intrinsicWidth = indicator.getIntrinsicWidth();
|
||||
final int intrinsicHeight = indicator.getIntrinsicHeight();
|
||||
final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
|
||||
final float boundAspect = (float) w / h;
|
||||
if (intrinsicAspect != boundAspect) {
|
||||
if (boundAspect > intrinsicAspect) {
|
||||
// New width is larger. Make it smaller to match height.
|
||||
final int width = (int) (h * intrinsicAspect);
|
||||
left = (w - width) / 2;
|
||||
right = left + width;
|
||||
} else {
|
||||
// New height is larger. Make it smaller to match width.
|
||||
final int height = (int) (w * (1 / intrinsicAspect));
|
||||
top = (h - height) / 2;
|
||||
bottom = top + height;
|
||||
}
|
||||
}
|
||||
indicator.setBounds(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
drawTrack(canvas);
|
||||
}
|
||||
|
||||
void drawTrack(Canvas canvas) {
|
||||
final Drawable d = indicator;
|
||||
if (d != null) {
|
||||
// Translate canvas so a indeterminate circular progress bar with padding
|
||||
// rotates properly in its animation
|
||||
final int saveCount = canvas.save();
|
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
d.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
|
||||
if (mShouldStartAnimationDrawable && d instanceof Animatable) {
|
||||
((Animatable) d).start();
|
||||
mShouldStartAnimationDrawable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int dw = 0;
|
||||
int dh = 0;
|
||||
|
||||
final Drawable d = indicator;
|
||||
if (d != null) {
|
||||
dw = Math.max(minWidth, Math.min(maxWidth, d.getIntrinsicWidth()));
|
||||
dh = Math.max(minHeight, Math.min(maxHeight, d.getIntrinsicHeight()));
|
||||
}
|
||||
|
||||
updateDrawableState();
|
||||
|
||||
dw += getPaddingLeft() + getPaddingRight();
|
||||
dh += getPaddingTop() + getPaddingBottom();
|
||||
|
||||
final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
|
||||
final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
|
||||
setMeasuredDimension(measuredWidth, measuredHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
updateDrawableState();
|
||||
}
|
||||
|
||||
private void updateDrawableState() {
|
||||
final int[] state = getDrawableState();
|
||||
if (indicator != null && indicator.isStateful()) {
|
||||
indicator.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void drawableHotspotChanged(float x, float y) {
|
||||
super.drawableHotspotChanged(x, y);
|
||||
|
||||
if (indicator != null) {
|
||||
indicator.setHotspot(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
startAnimation();
|
||||
removeCallbacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
stopAnimation();
|
||||
// This should come after stopAnimation(), otherwise an invalidate message remains in the
|
||||
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
|
||||
super.onDetachedFromWindow();
|
||||
removeCallbacks();
|
||||
}
|
||||
|
||||
private void removeCallbacks() {
|
||||
removeCallbacks(mDelayedHide);
|
||||
removeCallbacks(mDelayedShow);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.aurora.store.view.custom.progress.indicators
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import java.util.*
|
||||
|
||||
class BallPulseIndicator : Indicator() {
|
||||
|
||||
private val scaleFloats = floatArrayOf(
|
||||
0.75f,
|
||||
1.0f,
|
||||
0.75f
|
||||
)
|
||||
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
val circleSpacing = 4f
|
||||
val radius = (width.coerceAtMost(height) - circleSpacing * 2) / 6
|
||||
val x = width / 2f - (radius * 2 + circleSpacing)
|
||||
val y = height / 2f
|
||||
|
||||
for (i in 0..2) {
|
||||
canvas.save()
|
||||
val translateX = x + radius * 2 * i + circleSpacing * i
|
||||
canvas.translate(translateX, y)
|
||||
canvas.scale(scaleFloats[i], scaleFloats[i])
|
||||
canvas.drawCircle(0f, 0f, radius, paint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAnimators(): ArrayList<ValueAnimator> {
|
||||
val animators = ArrayList<ValueAnimator>()
|
||||
val delays = intArrayOf(120, 240, 360)
|
||||
|
||||
for (i in 0..2) {
|
||||
val scaleAnim = ValueAnimator.ofFloat(1f, 0.3f, 1f)
|
||||
scaleAnim.duration = 750
|
||||
scaleAnim.repeatCount = -1
|
||||
scaleAnim.startDelay = delays[i].toLong()
|
||||
|
||||
addUpdateListener(scaleAnim) { animation: ValueAnimator ->
|
||||
scaleFloats[i] = animation.animatedValue as Float
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
animators.add(scaleAnim)
|
||||
}
|
||||
return animators
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.aurora.store.view.custom.progress.indicators;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public abstract class Indicator extends Drawable implements Animatable {
|
||||
|
||||
private static final Rect ZERO_BOUNDS_RECT = new Rect();
|
||||
|
||||
private final HashMap<ValueAnimator, ValueAnimator.AnimatorUpdateListener> updateListenerHashMap = new HashMap<>();
|
||||
private final Paint paint = new Paint();
|
||||
|
||||
private int alpha = 255;
|
||||
private ArrayList<ValueAnimator> animators;
|
||||
private boolean hasAnimators;
|
||||
|
||||
protected Rect drawBounds = ZERO_BOUNDS_RECT;
|
||||
|
||||
public Indicator() {
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return paint.getColor();
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
paint.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
draw(canvas, paint);
|
||||
}
|
||||
|
||||
public abstract void draw(Canvas canvas, Paint paint);
|
||||
|
||||
public abstract ArrayList<ValueAnimator> onCreateAnimators();
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ensureAnimators();
|
||||
|
||||
if (animators == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the animators has not ended, do nothing.
|
||||
if (isStarted()) {
|
||||
return;
|
||||
}
|
||||
startAnimators();
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private void startAnimators() {
|
||||
for (int i = 0; i < animators.size(); i++) {
|
||||
ValueAnimator animator = animators.get(i);
|
||||
|
||||
//when the animator restart , add the updateListener again because they
|
||||
// was removed by animator stop .
|
||||
ValueAnimator.AnimatorUpdateListener updateListener = updateListenerHashMap.get(animator);
|
||||
if (updateListener != null) {
|
||||
animator.addUpdateListener(updateListener);
|
||||
}
|
||||
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAnimators() {
|
||||
if (animators != null) {
|
||||
for (ValueAnimator animator : animators) {
|
||||
if (animator != null && animator.isStarted()) {
|
||||
animator.removeAllUpdateListeners();
|
||||
animator.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAnimators() {
|
||||
if (!hasAnimators) {
|
||||
animators = onCreateAnimators();
|
||||
hasAnimators = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stopAnimators();
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
for (ValueAnimator animator : animators) {
|
||||
return animator.isStarted();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
for (ValueAnimator animator : animators) {
|
||||
return animator.isRunning();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener) {
|
||||
updateListenerHashMap.put(animator, updateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
setDrawBounds(bounds);
|
||||
}
|
||||
|
||||
public void setDrawBounds(Rect drawBounds) {
|
||||
setDrawBounds(drawBounds.left, drawBounds.top, drawBounds.right, drawBounds.bottom);
|
||||
}
|
||||
|
||||
public void setDrawBounds(int left, int top, int right, int bottom) {
|
||||
this.drawBounds = new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public void postInvalidate() {
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public Rect getDrawBounds() {
|
||||
return drawBounds;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return drawBounds.width();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return drawBounds.height();
|
||||
}
|
||||
|
||||
public int centerX() {
|
||||
return drawBounds.centerX();
|
||||
}
|
||||
|
||||
public int centerY() {
|
||||
return drawBounds.centerY();
|
||||
}
|
||||
|
||||
public float exactCenterX() {
|
||||
return drawBounds.exactCenterX();
|
||||
}
|
||||
|
||||
public float exactCenterY() {
|
||||
return drawBounds.exactCenterY();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,9 +29,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.extensions.*
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.data.models.File
|
||||
@@ -42,13 +40,15 @@ import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.SessionEvent
|
||||
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.databinding.ActivityDetailsBinding
|
||||
import com.aurora.store.util.*
|
||||
import com.aurora.extensions.*
|
||||
import com.aurora.store.view.custom.layouts.button.ActionButton
|
||||
import com.aurora.store.view.ui.downloads.DownloadActivity
|
||||
import com.aurora.store.view.ui.sheets.InstallErrorDialogSheet
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
@@ -102,13 +102,29 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: BusEvent) {
|
||||
fun onEventMainThread(event: Any) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent -> {
|
||||
attachActions()
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
}
|
||||
}
|
||||
is BusEvent.UninstallEvent -> {
|
||||
attachActions()
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
}
|
||||
}
|
||||
is SessionEvent.Failed -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
InstallErrorDialogSheet.newInstance(
|
||||
app,
|
||||
event.packageName,
|
||||
event.error,
|
||||
event.extra
|
||||
).show(supportFragmentManager, "SED")
|
||||
attachActions()
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
@@ -208,6 +224,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
private fun updateActionState(state: ActionButton.State) {
|
||||
B.layoutDetailsInstall.btnDownload.updateState(state)
|
||||
}
|
||||
|
||||
private fun openApp() {
|
||||
val intent = PackageUtil.getLaunchIntent(this, app.packageName)
|
||||
if (intent != null) {
|
||||
@@ -221,8 +241,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
@Synchronized
|
||||
private fun install(files: List<Download>) {
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
|
||||
task {
|
||||
AppInstaller.with(this)
|
||||
AppInstaller(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
app.packageName,
|
||||
@@ -241,7 +263,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
@Synchronized
|
||||
private fun uninstallApp() {
|
||||
AppInstaller.with(this)
|
||||
AppInstaller(this)
|
||||
.getPreferredInstaller()
|
||||
.uninstall(app.packageName)
|
||||
}
|
||||
@@ -363,6 +385,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
private fun purchase() {
|
||||
updateActionState(ActionButton.State.PROGRESS)
|
||||
task {
|
||||
val authData = AuthProvider
|
||||
.with(this)
|
||||
@@ -376,9 +399,11 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
enqueue(it)
|
||||
} else {
|
||||
Log.e("Failed to download : ${app.displayName}")
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
}
|
||||
} failUi {
|
||||
expandBottomSheet(it.message)
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
Log.e("Failed to purchase ${app.displayName} : ${it.message}")
|
||||
}
|
||||
}
|
||||
@@ -402,6 +427,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
Log.i("Downloading Apks : %s", app.displayName)
|
||||
}
|
||||
} else {
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
expandBottomSheet(getString(R.string.purchase_no_file))
|
||||
}
|
||||
}
|
||||
@@ -443,8 +469,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
B.layoutDetailsInstall.txtPurchaseError.text = message
|
||||
B.layoutDetailsInstall.btnDownload.updateProgress(false)
|
||||
with(B.layoutDetailsInstall) {
|
||||
txtPurchaseError.text = message
|
||||
btnDownload.updateState(ActionButton.State.IDLE)
|
||||
if (app.isFree)
|
||||
btnDownload.setText(R.string.action_install)
|
||||
else
|
||||
btnDownload.setText(app.price)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndSetupInstall() {
|
||||
@@ -459,39 +491,40 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
)
|
||||
|
||||
if (isUpdatable) {
|
||||
btn.setText(getString(R.string.action_update))
|
||||
btn.setText(R.string.action_update)
|
||||
btn.addOnClickListener {
|
||||
btn.updateProgress(true)
|
||||
startDownload()
|
||||
}
|
||||
} else {
|
||||
btn.setText(getString(R.string.action_open))
|
||||
btn.setText(R.string.action_open)
|
||||
btn.addOnClickListener { openApp() }
|
||||
}
|
||||
} else {
|
||||
if (app.isFree) {
|
||||
btn.setText(getString(R.string.action_install))
|
||||
btn.setText(R.string.action_install)
|
||||
} else {
|
||||
btn.setText(app.price)
|
||||
}
|
||||
|
||||
btn.addOnClickListener {
|
||||
btn.setText(getString(R.string.download_metadata))
|
||||
btn.updateProgress(true)
|
||||
btn.setText(R.string.download_metadata)
|
||||
startDownload()
|
||||
}
|
||||
}
|
||||
|
||||
btn.updateProgress(false)
|
||||
btn.updateState(ActionButton.State.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun flip(nextView: Int) {
|
||||
runOnUiThread {
|
||||
B.layoutDetailsInstall.viewFlipper.displayedChild = nextView
|
||||
if (nextView == 0)
|
||||
checkAndSetupInstall()
|
||||
val displayChild = B.layoutDetailsInstall.viewFlipper.displayedChild
|
||||
if (displayChild != nextView) {
|
||||
B.layoutDetailsInstall.viewFlipper.displayedChild = nextView
|
||||
if (nextView == 0)
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,8 +107,7 @@ class AppMenuSheet : BaseBottomSheet() {
|
||||
}
|
||||
|
||||
R.id.action_uninstall -> {
|
||||
AppInstaller
|
||||
.with(requireContext())
|
||||
AppInstaller(requireContext())
|
||||
.getPreferredInstaller().uninstall(app.packageName)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
lateinit var VM: SheetBaseBinding
|
||||
|
||||
var gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create()
|
||||
var gson: Gson = GsonBuilder()
|
||||
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
|
||||
.create()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val bottomSheetDialog = BottomSheetDialog(
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.sheets
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.copyToClipBoard
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.SheetInstallErrorBinding
|
||||
import com.bumptech.glide.load.resource.bitmap.CircleCrop
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
class InstallErrorDialogSheet : BaseBottomSheet() {
|
||||
|
||||
private lateinit var B: SheetInstallErrorBinding
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var title: String
|
||||
private lateinit var error: String
|
||||
private lateinit var extra: String
|
||||
|
||||
private var rawApp = String()
|
||||
|
||||
companion object {
|
||||
private const val DIALOG_TITLE = "DIALOG_TITLE"
|
||||
private const val DIALOG_ERROR = "DIALOG_ERROR"
|
||||
private const val DIALOG_EXTRA = "DIALOG_EXTRA"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(
|
||||
app: App,
|
||||
title: String?,
|
||||
error: String?,
|
||||
extra: String?
|
||||
): InstallErrorDialogSheet {
|
||||
return InstallErrorDialogSheet().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(Constants.STRING_EXTRA, gson.toJson(app))
|
||||
putString(DIALOG_TITLE, title)
|
||||
putString(DIALOG_ERROR, error)
|
||||
putString(DIALOG_EXTRA, extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateContentView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
B = SheetInstallErrorBinding.inflate(inflater, container, false)
|
||||
|
||||
val bundle = arguments
|
||||
bundle?.let {
|
||||
rawApp = bundle.getString(Constants.STRING_EXTRA, "{}")
|
||||
app = gson.fromJson(rawApp, App::class.java)
|
||||
title = bundle.getString(DIALOG_TITLE, "")
|
||||
error = bundle.getString(DIALOG_ERROR, "")
|
||||
extra = bundle.getString(DIALOG_EXTRA, "")
|
||||
|
||||
if (app.packageName.isNotEmpty()) {
|
||||
inflateData()
|
||||
} else {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
attachAction()
|
||||
|
||||
return B.root
|
||||
}
|
||||
|
||||
override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
}
|
||||
|
||||
private fun inflateData() {
|
||||
B.imgIcon.load(app.iconArtwork.url) {
|
||||
transform(CircleCrop())
|
||||
}
|
||||
|
||||
B.txtLine1.text = app.displayName
|
||||
B.txtLine2.text = error
|
||||
B.txtLine3.text = extra
|
||||
}
|
||||
|
||||
private fun attachAction() {
|
||||
B.btnPrimary.setOnClickListener {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
B.btnSecondary.setOnClickListener {
|
||||
if (::extra.isInitialized) {
|
||||
requireContext().copyToClipBoard(extra)
|
||||
requireContext().toast(R.string.toast_clipboard_copied)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,16 +93,22 @@ class UpdatesFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fetch.addListener(fetchListener)
|
||||
|
||||
return B.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
|
||||
fetch.addListener(fetchListener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
|
||||
fetch.removeListener(fetchListener)
|
||||
}
|
||||
super.onDestroyView()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -249,7 +255,7 @@ class UpdatesFragment : BaseFragment() {
|
||||
@Synchronized
|
||||
private fun install(packageName: String, files: List<Download>) {
|
||||
task {
|
||||
AppInstaller.with(requireContext())
|
||||
AppInstaller(requireContext())
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
packageName,
|
||||
|
||||
28
app/src/main/res/drawable/ic_install.xml
Normal file
28
app/src/main/res/drawable/ic_install.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,2v2L5,4l-0.001,10h14L19,4h-4L15,2h5a1,1 0,0 1,1 1v18a1,1 0,0 1,-1 1L4,22a1,1 0,0 1,-1 -1L3,3a1,1 0,0 1,1 -1h5zM18.999,16h-14L5,20h14l-0.001,-4zM17,17v2h-2v-2h2zM13,2v5h3l-4,4 -4,-4h3L11,2h2z"/>
|
||||
</vector>
|
||||
@@ -137,7 +137,7 @@
|
||||
android:text="@string/account_login_using"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_google"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -146,7 +146,7 @@
|
||||
app:btnStateIcon="@drawable/ic_google"
|
||||
app:btnStateText="@string/account_google" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_anonymous"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -187,7 +187,7 @@
|
||||
android:text="@string/account_logout"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_logout"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
android:text="@string/account_login_using"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_google"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -105,7 +105,7 @@
|
||||
app:btnStateIcon="@drawable/ic_google"
|
||||
app:btnStateText="@string/account_google" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_anonymous"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -36,9 +36,13 @@
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:animateFirstView="true"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.aurora.store.view.custom.ActionButton
|
||||
<com.aurora.store.view.custom.layouts.button.ActionButton
|
||||
android:id="@+id/btn_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_button"
|
||||
@@ -114,7 +118,6 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_cancel"
|
||||
android:layout_width="@dimen/icon_size_small"
|
||||
@@ -126,7 +129,6 @@
|
||||
android:padding="@dimen/padding_medium"
|
||||
app:srcCompat="@drawable/ic_cancel" />
|
||||
</RelativeLayout>
|
||||
|
||||
</ViewFlipper>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
android:text="@string/account_login_using"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_google"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -140,7 +140,7 @@
|
||||
app:btnStateIcon="@drawable/ic_google"
|
||||
app:btnStateText="@string/account_google" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_anonymous"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -181,7 +181,7 @@
|
||||
android:text="@string/account_logout"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_logout"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
android:text="@string/account_login_using"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_google"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -98,7 +98,7 @@
|
||||
app:btnStateIcon="@drawable/ic_google"
|
||||
app:btnStateText="@string/account_google" />
|
||||
|
||||
<com.aurora.store.view.custom.StateButton
|
||||
<com.aurora.store.view.custom.layouts.button.StateButton
|
||||
android:id="@+id/btn_anonymous"
|
||||
android:layout_width="@dimen/width_button"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -36,9 +36,13 @@
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:animateFirstView="true"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.aurora.store.view.custom.ActionButton
|
||||
<com.aurora.store.view.custom.layouts.button.ActionButton
|
||||
android:id="@+id/btn_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_button"
|
||||
@@ -113,7 +117,6 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_cancel"
|
||||
android:layout_width="@dimen/icon_size_small"
|
||||
@@ -125,7 +128,6 @@
|
||||
android:padding="@dimen/padding_medium"
|
||||
app:srcCompat="@drawable/ic_cancel" />
|
||||
</RelativeLayout>
|
||||
|
||||
</ViewFlipper>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
110
app/src/main/res/layout/sheet_install_error.xml
Normal file
110
app/src/main/res/layout/sheet_install_error.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_large"
|
||||
android:showDividers="middle">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="@dimen/icon_size_category"
|
||||
android:layout_height="@dimen/icon_size_category"
|
||||
android:layout_centerVertical="true"
|
||||
tools:src="@drawable/bg_circle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginBottom="@dimen/margin_normal"
|
||||
android:layout_toEndOf="@id/img_icon"
|
||||
android:maxLines="1"
|
||||
android:text="@string/title_installer"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="3"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
tools:text="Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="10"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
android:textColor="@color/colorRed"
|
||||
tools:text="Error" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="10"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
tools:text="Extra" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_secondary"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_copy" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_primary"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_ok" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -21,37 +21,59 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="@dimen/height_button">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:paddingVertical="@dimen/padding_normal"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle"
|
||||
android:textColor="@color/colorWhite"
|
||||
app:iconPadding="@dimen/padding_large"
|
||||
app:iconTint="?colorAccent"
|
||||
tools:text="Install" />
|
||||
android:layout_height="match_parent"
|
||||
android:animateFirstView="true"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="@dimen/icon_size_default"
|
||||
android:layout_height="@dimen/icon_size_default"
|
||||
android:layout_alignEnd="@id/btn"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/icon_size_default"
|
||||
android:elevation="8dp"
|
||||
android:indeterminateTint="@color/colorWhite"
|
||||
android:progressTint="@color/colorWhite"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible" />
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle"
|
||||
app:iconPadding="@dimen/padding_large"
|
||||
app:iconTint="@color/colorWhite"
|
||||
tools:text="Install" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.aurora.store.view.custom.progress.AuroraProgressView
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="@dimen/icon_size_small"
|
||||
android:layout_height="@dimen/icon_size_small"
|
||||
android:layout_centerInParent="true"
|
||||
tools:indicatorColor="@color/colorAccent" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
app:srcCompat="@drawable/ic_check"
|
||||
app:tint="@color/colorWhite" />
|
||||
</RelativeLayout>
|
||||
</ViewFlipper>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_copy"
|
||||
android:title="@string/action_copy" />
|
||||
android:title="@string/action_copy_link" />
|
||||
<item
|
||||
android:id="@+id/action_pause"
|
||||
android:title="@string/action_pause" />
|
||||
|
||||
@@ -38,6 +38,24 @@
|
||||
|
||||
<declare-styleable name="ActionButton">
|
||||
<attr name="btnActionText" format="string" />
|
||||
<attr name="btnActionTextColor" format="string" />
|
||||
<attr name="btnActionIcon" format="string" />
|
||||
<attr name="btnActionBackground" format="enum">
|
||||
<enum name="outlined" value="0" />
|
||||
<enum name="roundedOutlined" value="1" />
|
||||
<enum name="flat" value="2" />
|
||||
<enum name="roundedFlat" value="3" />
|
||||
<enum name="none" value="4" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AuroraProgressView">
|
||||
<attr name="minWidth" format="dimension" />
|
||||
<attr name="maxWidth" format="dimension" />
|
||||
<attr name="minHeight" format="dimension" />
|
||||
<attr name="maxHeight" format="dimension" />
|
||||
<attr name="indicatorName" format="string" />
|
||||
<attr name="indicatorColor" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DevInfoLayout">
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
<string name="action_clear">"Clear"</string>
|
||||
<string name="action_clear_all">"Clear all"</string>
|
||||
<string name="action_clear_restart">"Clear data"</string>
|
||||
<string name="action_copy">"Copy Link"</string>
|
||||
<string name="action_close">"Close"</string>
|
||||
<string name="action_copy">"Copy"</string>
|
||||
<string name="action_copy_link">"Copy Link"</string>
|
||||
<string name="action_disable">"Disable"</string>
|
||||
<string name="action_discard">"Discard"</string>
|
||||
<string name="action_discover">"Discover"</string>
|
||||
@@ -92,6 +94,7 @@
|
||||
<string name="action_manual">"Manual download"</string>
|
||||
<string name="action_more">"More"</string>
|
||||
<string name="action_next">"Next"</string>
|
||||
<string name="action_ok">"Okay"</string>
|
||||
<string name="action_open">"Open"</string>
|
||||
<string name="action_pause">"Pause"</string>
|
||||
<string name="action_pending">"Pending"</string>
|
||||
@@ -201,9 +204,12 @@
|
||||
|
||||
<string name="installer_root_available">"Root access available"</string>
|
||||
<string name="installer_root_unavailable">"No Root, grant root access or change the installer."</string>
|
||||
<string name="installer_service_available">"Aurora services is available and ready to install."</string>
|
||||
<string name="installer_service_unavailable">"Aurora services not configured. Make sure service is enabled & all permissions are granted"</string>
|
||||
<string name="installer_status_failure">"Installation failed"</string>
|
||||
<string name="installer_status_failure_aborted">"Installation cancelled"</string>
|
||||
<string name="installer_status_failure_blocked">"Installation was blocked"</string>
|
||||
<string name="installer_status_failure_session">"Could not create session"</string>
|
||||
<string name="installer_status_failure_conflict">"Conflicting package exists"</string>
|
||||
<string name="installer_status_failure_incompatible">"Incompatible app"</string>
|
||||
<string name="installer_status_failure_invalid">"Invalid or corrupted APK"</string>
|
||||
@@ -290,6 +296,7 @@
|
||||
<string name="title_games">"Games"</string>
|
||||
<string name="title_installation">"Installation"</string>
|
||||
<string name="title_installed">"Installed"</string>
|
||||
<string name="title_installer">"App Installer"</string>
|
||||
<string name="title_language">"Language"</string>
|
||||
<string name="title_library">"Library"</string>
|
||||
<string name="title_purchase_history">"Purchase history"</string>
|
||||
|
||||
@@ -72,4 +72,28 @@
|
||||
<style name="Aurora.BottomSheetDialog" parent="Theme.Design.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/Aurora.BottomSheetDialogStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="AuroraProgressView">
|
||||
<item name="minWidth">32dp</item>
|
||||
<item name="maxWidth">46dp</item>
|
||||
<item name="minHeight">36dp</item>
|
||||
<item name="maxHeight">36dp</item>
|
||||
<item name="indicatorName">BallPulseIndicator</item>
|
||||
</style>
|
||||
|
||||
<style name="AuroraProgressView.Large">
|
||||
<item name="minWidth">48dp</item>
|
||||
<item name="maxWidth">48dp</item>
|
||||
<item name="minHeight">56dp</item>
|
||||
<item name="maxHeight">56dp</item>
|
||||
<item name="indicatorName">BallPulseIndicator</item>
|
||||
</style>
|
||||
|
||||
<style name="AuroraProgressView.Small">
|
||||
<item name="minWidth">24dp</item>
|
||||
<item name="maxWidth">24dp</item>
|
||||
<item name="minHeight">24dp</item>
|
||||
<item name="maxHeight">24dp</item>
|
||||
<item name="indicatorName">BallPulseIndicator</item>
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user