Improve installer & related UI

This commit is contained in:
Rahul Kumar Patel
2021-02-25 08:57:30 +05:30
parent b877552634
commit 0e1ae6364d
34 changed files with 1379 additions and 158 deletions

View File

@@ -20,6 +20,8 @@
package com.aurora.extensions package com.aurora.extensions
import android.text.format.DateFormat import android.text.format.DateFormat
import java.io.PrintWriter
import java.io.StringWriter
import java.util.* import java.util.*
fun Long.toDate(): String { fun Long.toDate(): String {
@@ -27,3 +29,11 @@ fun Long.toDate(): String {
calendar.timeInMillis = this calendar.timeInMillis = this
return DateFormat.format("dd/MM/yy", calendar).toString() 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()
}

View File

@@ -36,6 +36,10 @@ class AuroraApplication : MultiDexApplication() {
private lateinit var fetch: Fetch private lateinit var fetch: Fetch
private lateinit var packageManagerReceiver: PackageManagerReceiver private lateinit var packageManagerReceiver: PackageManagerReceiver
companion object{
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View File

@@ -28,4 +28,17 @@ sealed class BusEvent {
var email: String = String(), var email: String = String(),
var aasToken: String = String() var aasToken: String = String()
) : BusEvent() ) : 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()
} }

View File

@@ -25,9 +25,7 @@ import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
open class AppInstaller private constructor(var context: Context) { open class AppInstaller constructor(var context: Context) {
companion object : SingletonHolder<AppInstaller, Context>(::AppInstaller)
fun getPreferredInstaller(): IInstaller { fun getPreferredInstaller(): IInstaller {
val prefValue = Preferences.getInteger( val prefValue = Preferences.getInteger(

View File

@@ -24,24 +24,22 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.aurora.store.AuroraApplication
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import java.io.File import java.io.File
private val enqueuedInstalls: MutableSet<String> = mutableSetOf()
abstract class InstallerBase(protected var context: Context) : IInstaller { abstract class InstallerBase(protected var context: Context) : IInstaller {
override fun clearQueue() { override fun clearQueue() {
enqueuedInstalls.clear() AuroraApplication.enqueuedInstalls.clear()
} }
override fun isAlreadyQueued(packageName: String): Boolean { override fun isAlreadyQueued(packageName: String): Boolean {
return enqueuedInstalls.contains(packageName) return AuroraApplication.enqueuedInstalls.contains(packageName)
} }
override fun removeFromInstallQueue(packageName: String) { override fun removeFromInstallQueue(packageName: String) {
enqueuedInstalls.remove(packageName) AuroraApplication.enqueuedInstalls.remove(packageName)
} }
override fun uninstall(packageName: String) { override fun uninstall(packageName: String) {

View File

@@ -25,52 +25,69 @@ import android.content.pm.PackageInstaller
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.annotation.RequiresApi 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() { class InstallerService : Service() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { 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) val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
//Send broadcast for the installation status of the package when (status) {
sendStatusBroadcast(status, packageName) PackageInstaller.STATUS_PENDING_USER_ACTION -> promptUser(intent)
else -> postStatus(status, packageName, extra)
//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)
}
} }
stopSelf() stopSelf()
return START_NOT_STICKY return START_NOT_STICKY
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun promptUser(intent: Intent) {
private fun sendStatusBroadcast(status: Int, packageName: String?) { val confirmationIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
if (StringUtils.isNotEmpty(packageName)) {
val statusIntent = Intent(ACTION_SESSION_INSTALLER) confirmationIntent?.let {
statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status) it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName) it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
sendBroadcast(statusIntent) 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? { override fun onBind(intent: Intent): IBinder? {
return null return null
} }
companion object {
private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER"
}
} }

View File

@@ -20,11 +20,13 @@
package com.aurora.store.data.installer package com.aurora.store.data.installer
import android.content.Context import android.content.Context
import com.aurora.store.R
import com.aurora.store.util.Log
import com.aurora.extensions.isLAndAbove import com.aurora.extensions.isLAndAbove
import com.aurora.extensions.toast 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 com.topjohnwu.superuser.Shell
import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -50,7 +52,12 @@ class RootInstaller(context: Context) : InstallerBase(context) {
xInstallLegacy(packageName, it) xInstallLegacy(packageName, it)
} }
} else { } 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 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<") Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
} }
} }
@@ -80,12 +87,34 @@ class RootInstaller(context: Context) : InstallerBase(context) {
.exec() .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 { } else {
removeFromInstallQueue(packageName) 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 { } else {
removeFromInstallQueue(packageName) 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) removeFromInstallQueue(packageName)
} }
} }
private fun parseError(result: Shell.Result): String {
return result.err.joinToString(separator = "\n")
}
} }

View File

@@ -32,7 +32,10 @@ import androidx.core.content.FileProvider
import com.aurora.services.IPrivilegedCallback import com.aurora.services.IPrivilegedCallback
import com.aurora.services.IPrivilegedService import com.aurora.services.IPrivilegedService
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.event.SessionEvent
import com.aurora.store.util.Log import com.aurora.store.util.Log
import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
class ServiceInstaller(context: Context) : InstallerBase(context) { class ServiceInstaller(context: Context) : InstallerBase(context) {
@@ -65,27 +68,51 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun xInstall(packageName: String, uriList: List<Uri>) { private fun xInstall(packageName: String, uriList: List<Uri>) {
val serviceConnection = object : ServiceConnection { val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) { override fun onServiceConnected(name: ComponentName, binder: IBinder) {
val service = IPrivilegedService.Stub.asInterface(binder) val service = IPrivilegedService.Stub.asInterface(binder)
val callback = object : IPrivilegedCallback.Stub() {
override fun handleResult(packageName: String, returnCode: Int) {
removeFromInstallQueue(packageName)
}
}
try { if (service.hasPrivilegedPermissions()) {
service.installSplitPackage( Log.i(context.getString(R.string.installer_service_available))
packageName, val callback = object : IPrivilegedCallback.Stub() {
uriList, override fun handleResult(packageName: String, returnCode: Int) {
ACTION_INSTALL_REPLACE_EXISTING, Log.e("$packageName : $returnCode")
BuildConfig.APPLICATION_ID, removeFromInstallQueue(packageName)
callback }
) }
} catch (e: RemoteException) {
removeFromInstallQueue(packageName) try {
Log.e("Failed to connect Aurora Services") 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)
)
)
} }
} }

View File

@@ -27,9 +27,12 @@ import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.aurora.extensions.isNAndAbove
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.data.event.SessionEvent
import com.aurora.store.util.Log import com.aurora.store.util.Log
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
class SessionInstaller(context: Context) : InstallerBase(context) { class SessionInstaller(context: Context) : InstallerBase(context) {
@@ -57,18 +60,22 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun xInstall(packageName: String, uriList: List<Uri>) { private fun xInstall(packageName: String, uriList: List<Uri>) {
val packageInstaller = context.packageManager.packageInstaller 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 sessionId = packageInstaller.createSession(sessionParams)
val session = packageInstaller.openSession(sessionId) val session = packageInstaller.openSession(sessionId)
try { try {
Log.i("Writing splits to session for $packageName") Log.i("Writing splits to session for $packageName")
var apkId = 1
for (uri in uriList) { for (uri in uriList) {
val inputStream = context.contentResolver.openInputStream(uri) val inputStream = context.contentResolver.openInputStream(uri)
val outputStream = session.openWrite( val outputStream = session.openWrite(
"${packageName}_${apkId++}", "${packageName}_${System.currentTimeMillis()}",
0, 0,
-1 -1
) )
@@ -81,11 +88,11 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
IOUtils.close(outputStream) IOUtils.close(outputStream)
} }
val intent = Intent(context, InstallerService::class.java) val callBackIntent = Intent(context, InstallerService::class.java)
val pendingIntent = PendingIntent.getService( val pendingIntent = PendingIntent.getService(
context, context,
sessionId, sessionId,
intent, callBackIntent,
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
) )
@@ -95,6 +102,12 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
} catch (e: Exception) { } catch (e: Exception) {
session.abandon() session.abandon()
removeFromInstallQueue(packageName) removeFromInstallQueue(packageName)
val event = SessionEvent.Failed(
packageName,
e.localizedMessage,
e.stackTraceToString()
)
EventBus.getDefault().post(event)
Log.e("Failed to install $packageName : %s", e.message) Log.e("Failed to install $packageName : %s", e.message)
} }
} }

View File

@@ -43,7 +43,7 @@ open class PackageManagerReceiver : BroadcastReceiver() {
.post(InstallEvent(packageName, "")) .post(InstallEvent(packageName, ""))
//Clear installation queue //Clear installation queue
AppInstaller.with(context) AppInstaller(context)
.getPreferredInstaller() .getPreferredInstaller()
.removeFromInstallQueue(packageName) .removeFromInstallQueue(packageName)
} }

View File

@@ -29,10 +29,12 @@ import android.util.ArrayMap
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.aurora.Constants import com.aurora.Constants
import com.aurora.extensions.getStyledAttributeColor
import com.aurora.extensions.isLAndAbove import com.aurora.extensions.isLAndAbove
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager 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.installer.AppInstaller
import com.aurora.store.data.receiver.DownloadCancelReceiver import com.aurora.store.data.receiver.DownloadCancelReceiver
import com.aurora.store.data.receiver.DownloadPauseReceiver import com.aurora.store.data.receiver.DownloadPauseReceiver
@@ -46,6 +48,8 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.* import com.tonyodev.fetch2.*
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.util.* import java.util.*
@@ -83,6 +87,8 @@ class NotificationService : Service() {
Log.i("Notification Service Started") Log.i("Notification Service Started")
EventBus.getDefault().register(this)
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
//Create Notification Channels : General & Alert //Create Notification Channels : General & Alert
@@ -97,9 +103,6 @@ class NotificationService : Service() {
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup) showNotification(groupId, download, fetchGroup)
if (fetchGroup.groupDownloadProgress == 100) {
install(download.tag!!, fetchGroup.downloads)
}
} }
override fun onError( override fun onError(
@@ -214,8 +217,8 @@ class NotificationService : Service() {
} }
val progress = fetchGroup.groupDownloadProgress val progress = fetchGroup.groupDownloadProgress
val progressBigText = NotificationCompat.BigTextStyle() val progressBigText = NotificationCompat.BigTextStyle()
when (status) {
when (status) {
Status.QUEUED -> { Status.QUEUED -> {
builder.setProgress(100, 0, true) builder.setProgress(100, 0, true)
progressBigText.bigText(getString(R.string.download_queued)) progressBigText.bigText(getString(R.string.download_queued))
@@ -308,7 +311,6 @@ class NotificationService : Service() {
app.id, app.id,
builder.build() builder.build()
) )
//}
} }
private fun getPauseIntent(groupId: Int): PendingIntent { 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 @Synchronized
private fun install(packageName: String, files: List<Download>) { private fun install(packageName: String, files: List<Download>) {
AppInstaller.with(this) AppInstaller(this)
.getPreferredInstaller() .getPreferredInstaller()
.install( .install(
packageName, 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() { override fun onDestroy() {
Log.i("Notification Service Stopped") Log.i("Notification Service Stopped")
fetch.removeListener(fetchListener) fetch.removeListener(fetchListener)
EventBus.getDefault().unregister(this);
super.onDestroy() super.onDestroy()
} }
} }

View File

@@ -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.Context
import android.content.res.ColorStateList
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.RequiresApi 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.R
import com.aurora.store.databinding.ViewActionButtonBinding import com.aurora.store.databinding.ViewActionButtonBinding
import nl.komponents.kovenant.task
import java.util.concurrent.TimeUnit
class ActionButton : RelativeLayout { class ActionButton : RelativeLayout {
@@ -64,23 +70,71 @@ class ActionButton : RelativeLayout {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton) val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton)
val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText) 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.text = btnTxt
B.btn.setTextColor(stateColor)
B.img.setImageDrawable(ContextCompat.getDrawable(context, stateIcon))
if (isLAndAbove()) {
B.img.imageTintList = ColorStateList.valueOf(stateColor)
}
typedArray.recycle() typedArray.recycle()
} }
fun setText(text: String) { fun setText(text: String) {
B.viewFlipper.displayedChild = 0
B.btn.text = text B.btn.text = text
} }
fun updateProgress(isVisible: Boolean) { fun setText(text: Int) {
if (isVisible) B.viewFlipper.displayedChild = 0
B.progress.visibility = View.VISIBLE B.btn.text = getString(text)
else }
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) { fun addOnClickListener(onClickListener: OnClickListener) {
B.btn.setOnClickListener(onClickListener) B.btn.setOnClickListener(onClickListener)
} }
enum class State {
IDLE,
PROGRESS,
COMPLETE,
}
} }

View File

@@ -17,7 +17,7 @@
* *
*/ */
package com.aurora.store.view.custom package com.aurora.store.view.custom.layouts.button
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}

View File

@@ -29,9 +29,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import com.aurora.Constants import com.aurora.Constants
import com.aurora.extensions.isLAndAbove import com.aurora.extensions.*
import com.aurora.extensions.load
import com.aurora.extensions.toast
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.File 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.DownloadManager
import com.aurora.store.data.downloader.RequestBuilder import com.aurora.store.data.downloader.RequestBuilder
import com.aurora.store.data.event.BusEvent 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.installer.AppInstaller
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.databinding.ActivityDetailsBinding import com.aurora.store.databinding.ActivityDetailsBinding
import com.aurora.store.util.* 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.downloads.DownloadActivity
import com.aurora.store.view.ui.sheets.InstallErrorDialogSheet
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
@@ -102,13 +102,29 @@ class AppDetailsActivity : BaseDetailsActivity() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: BusEvent) { fun onEventMainThread(event: Any) {
when (event) { when (event) {
is BusEvent.InstallEvent -> { is BusEvent.InstallEvent -> {
attachActions() if (app.packageName == event.packageName) {
attachActions()
}
} }
is BusEvent.UninstallEvent -> { 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 -> { else -> {
@@ -208,6 +224,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
checkAndSetupInstall() checkAndSetupInstall()
} }
private fun updateActionState(state: ActionButton.State) {
B.layoutDetailsInstall.btnDownload.updateState(state)
}
private fun openApp() { private fun openApp() {
val intent = PackageUtil.getLaunchIntent(this, app.packageName) val intent = PackageUtil.getLaunchIntent(this, app.packageName)
if (intent != null) { if (intent != null) {
@@ -221,8 +241,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
@Synchronized @Synchronized
private fun install(files: List<Download>) { private fun install(files: List<Download>) {
updateActionState(ActionButton.State.IDLE)
task { task {
AppInstaller.with(this) AppInstaller(this)
.getPreferredInstaller() .getPreferredInstaller()
.install( .install(
app.packageName, app.packageName,
@@ -241,7 +263,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
@Synchronized @Synchronized
private fun uninstallApp() { private fun uninstallApp() {
AppInstaller.with(this) AppInstaller(this)
.getPreferredInstaller() .getPreferredInstaller()
.uninstall(app.packageName) .uninstall(app.packageName)
} }
@@ -363,6 +385,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
} }
private fun purchase() { private fun purchase() {
updateActionState(ActionButton.State.PROGRESS)
task { task {
val authData = AuthProvider val authData = AuthProvider
.with(this) .with(this)
@@ -376,9 +399,11 @@ class AppDetailsActivity : BaseDetailsActivity() {
enqueue(it) enqueue(it)
} else { } else {
Log.e("Failed to download : ${app.displayName}") Log.e("Failed to download : ${app.displayName}")
updateActionState(ActionButton.State.IDLE)
} }
} failUi { } failUi {
expandBottomSheet(it.message) expandBottomSheet(it.message)
updateActionState(ActionButton.State.IDLE)
Log.e("Failed to purchase ${app.displayName} : ${it.message}") Log.e("Failed to purchase ${app.displayName} : ${it.message}")
} }
} }
@@ -402,6 +427,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
Log.i("Downloading Apks : %s", app.displayName) Log.i("Downloading Apks : %s", app.displayName)
} }
} else { } else {
updateActionState(ActionButton.State.IDLE)
expandBottomSheet(getString(R.string.purchase_no_file)) expandBottomSheet(getString(R.string.purchase_no_file))
} }
} }
@@ -443,8 +469,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
bottomSheetBehavior.isHideable = false bottomSheetBehavior.isHideable = false
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
B.layoutDetailsInstall.txtPurchaseError.text = message with(B.layoutDetailsInstall) {
B.layoutDetailsInstall.btnDownload.updateProgress(false) 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() { private fun checkAndSetupInstall() {
@@ -459,39 +491,40 @@ class AppDetailsActivity : BaseDetailsActivity() {
) )
if (isUpdatable) { if (isUpdatable) {
btn.setText(getString(R.string.action_update)) btn.setText(R.string.action_update)
btn.addOnClickListener { btn.addOnClickListener {
btn.updateProgress(true)
startDownload() startDownload()
} }
} else { } else {
btn.setText(getString(R.string.action_open)) btn.setText(R.string.action_open)
btn.addOnClickListener { openApp() } btn.addOnClickListener { openApp() }
} }
} else { } else {
if (app.isFree) { if (app.isFree) {
btn.setText(getString(R.string.action_install)) btn.setText(R.string.action_install)
} else { } else {
btn.setText(app.price) btn.setText(app.price)
} }
btn.addOnClickListener { btn.addOnClickListener {
btn.setText(getString(R.string.download_metadata)) btn.setText(R.string.download_metadata)
btn.updateProgress(true)
startDownload() startDownload()
} }
} }
btn.updateProgress(false) btn.updateState(ActionButton.State.IDLE)
} }
} }
@Synchronized @Synchronized
private fun flip(nextView: Int) { private fun flip(nextView: Int) {
runOnUiThread { runOnUiThread {
B.layoutDetailsInstall.viewFlipper.displayedChild = nextView val displayChild = B.layoutDetailsInstall.viewFlipper.displayedChild
if (nextView == 0) if (displayChild != nextView) {
checkAndSetupInstall() B.layoutDetailsInstall.viewFlipper.displayedChild = nextView
if (nextView == 0)
checkAndSetupInstall()
}
} }
} }

View File

@@ -107,8 +107,7 @@ class AppMenuSheet : BaseBottomSheet() {
} }
R.id.action_uninstall -> { R.id.action_uninstall -> {
AppInstaller AppInstaller(requireContext())
.with(requireContext())
.getPreferredInstaller().uninstall(app.packageName) .getPreferredInstaller().uninstall(app.packageName)
} }

View File

@@ -38,7 +38,9 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
lateinit var VM: SheetBaseBinding 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 { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val bottomSheetDialog = BottomSheetDialog( val bottomSheetDialog = BottomSheetDialog(

View File

@@ -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)
}
}
}
}

View File

@@ -93,16 +93,22 @@ class UpdatesFragment : BaseFragment() {
} }
} }
fetch.addListener(fetchListener)
return B.root 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) { if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.removeListener(fetchListener) fetch.removeListener(fetchListener)
} }
super.onDestroyView() super.onPause()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -249,7 +255,7 @@ class UpdatesFragment : BaseFragment() {
@Synchronized @Synchronized
private fun install(packageName: String, files: List<Download>) { private fun install(packageName: String, files: List<Download>) {
task { task {
AppInstaller.with(requireContext()) AppInstaller(requireContext())
.getPreferredInstaller() .getPreferredInstaller()
.install( .install(
packageName, packageName,

View 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>

View File

@@ -137,7 +137,7 @@
android:text="@string/account_login_using" android:text="@string/account_login_using"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_google" android:id="@+id/btn_google"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -146,7 +146,7 @@
app:btnStateIcon="@drawable/ic_google" app:btnStateIcon="@drawable/ic_google"
app:btnStateText="@string/account_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:id="@+id/btn_anonymous"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -187,7 +187,7 @@
android:text="@string/account_logout" android:text="@string/account_logout"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_logout" android:id="@+id/btn_logout"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -96,7 +96,7 @@
android:text="@string/account_login_using" android:text="@string/account_login_using"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_google" android:id="@+id/btn_google"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -105,7 +105,7 @@
app:btnStateIcon="@drawable/ic_google" app:btnStateIcon="@drawable/ic_google"
app:btnStateText="@string/account_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:id="@+id/btn_anonymous"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -36,9 +36,13 @@
<ViewFlipper <ViewFlipper
android:id="@+id/view_flipper" android:id="@+id/view_flipper"
android:layout_width="match_parent" 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:id="@+id/btn_download"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/height_button" android:layout_height="@dimen/height_button"
@@ -114,7 +118,6 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_cancel" android:id="@+id/img_cancel"
android:layout_width="@dimen/icon_size_small" android:layout_width="@dimen/icon_size_small"
@@ -126,7 +129,6 @@
android:padding="@dimen/padding_medium" android:padding="@dimen/padding_medium"
app:srcCompat="@drawable/ic_cancel" /> app:srcCompat="@drawable/ic_cancel" />
</RelativeLayout> </RelativeLayout>
</ViewFlipper> </ViewFlipper>
<LinearLayout <LinearLayout

View File

@@ -131,7 +131,7 @@
android:text="@string/account_login_using" android:text="@string/account_login_using"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_google" android:id="@+id/btn_google"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -140,7 +140,7 @@
app:btnStateIcon="@drawable/ic_google" app:btnStateIcon="@drawable/ic_google"
app:btnStateText="@string/account_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:id="@+id/btn_anonymous"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -181,7 +181,7 @@
android:text="@string/account_logout" android:text="@string/account_logout"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_logout" android:id="@+id/btn_logout"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -89,7 +89,7 @@
android:text="@string/account_login_using" android:text="@string/account_login_using"
android:textAlignment="center" /> android:textAlignment="center" />
<com.aurora.store.view.custom.StateButton <com.aurora.store.view.custom.layouts.button.StateButton
android:id="@+id/btn_google" android:id="@+id/btn_google"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -98,7 +98,7 @@
app:btnStateIcon="@drawable/ic_google" app:btnStateIcon="@drawable/ic_google"
app:btnStateText="@string/account_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:id="@+id/btn_anonymous"
android:layout_width="@dimen/width_button" android:layout_width="@dimen/width_button"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -36,9 +36,13 @@
<ViewFlipper <ViewFlipper
android:id="@+id/view_flipper" android:id="@+id/view_flipper"
android:layout_width="match_parent" 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:id="@+id/btn_download"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/height_button" android:layout_height="@dimen/height_button"
@@ -113,7 +117,6 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_cancel" android:id="@+id/img_cancel"
android:layout_width="@dimen/icon_size_small" android:layout_width="@dimen/icon_size_small"
@@ -125,7 +128,6 @@
android:padding="@dimen/padding_medium" android:padding="@dimen/padding_medium"
app:srcCompat="@drawable/ic_cancel" /> app:srcCompat="@drawable/ic_cancel" />
</RelativeLayout> </RelativeLayout>
</ViewFlipper> </ViewFlipper>
<LinearLayout <LinearLayout

View 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>

View File

@@ -21,37 +21,59 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="@dimen/height_button">
<com.google.android.material.button.MaterialButton <ViewFlipper
android:id="@+id/btn" android:id="@+id/view_flipper"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_centerHorizontal="true" android:animateFirstView="true"
android:paddingVertical="@dimen/padding_normal" android:inAnimation="@anim/fade_in"
android:paddingStart="24dp" android:outAnimation="@anim/fade_out"
android:paddingEnd="24dp" tools:ignore="UselessParent">
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.Aurora.SubTitle"
android:textColor="@color/colorWhite"
app:iconPadding="@dimen/padding_large"
app:iconTint="?colorAccent"
tools:text="Install" />
<androidx.core.widget.ContentLoadingProgressBar <RelativeLayout
android:id="@+id/progress" android:layout_width="match_parent"
style="?android:attr/progressBarStyle" android:layout_height="match_parent">
android:layout_width="@dimen/icon_size_default"
android:layout_height="@dimen/icon_size_default" <com.google.android.material.button.MaterialButton
android:layout_alignEnd="@id/btn" android:id="@+id/btn"
android:layout_centerVertical="true" style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
android:layout_marginEnd="@dimen/icon_size_default" android:layout_width="match_parent"
android:elevation="8dp" android:layout_height="match_parent"
android:indeterminateTint="@color/colorWhite" android:layout_centerHorizontal="true"
android:progressTint="@color/colorWhite" android:textAllCaps="true"
android:visibility="invisible" android:textAppearance="@style/TextAppearance.Aurora.SubTitle"
tools:visibility="visible" /> 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> </RelativeLayout>

View File

@@ -20,7 +20,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/action_copy" android:id="@+id/action_copy"
android:title="@string/action_copy" /> android:title="@string/action_copy_link" />
<item <item
android:id="@+id/action_pause" android:id="@+id/action_pause"
android:title="@string/action_pause" /> android:title="@string/action_pause" />

View File

@@ -38,6 +38,24 @@
<declare-styleable name="ActionButton"> <declare-styleable name="ActionButton">
<attr name="btnActionText" format="string" /> <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>
<declare-styleable name="DevInfoLayout"> <declare-styleable name="DevInfoLayout">

View File

@@ -56,7 +56,9 @@
<string name="action_clear">"Clear"</string> <string name="action_clear">"Clear"</string>
<string name="action_clear_all">"Clear all"</string> <string name="action_clear_all">"Clear all"</string>
<string name="action_clear_restart">"Clear data"</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_disable">"Disable"</string>
<string name="action_discard">"Discard"</string> <string name="action_discard">"Discard"</string>
<string name="action_discover">"Discover"</string> <string name="action_discover">"Discover"</string>
@@ -92,6 +94,7 @@
<string name="action_manual">"Manual download"</string> <string name="action_manual">"Manual download"</string>
<string name="action_more">"More"</string> <string name="action_more">"More"</string>
<string name="action_next">"Next"</string> <string name="action_next">"Next"</string>
<string name="action_ok">"Okay"</string>
<string name="action_open">"Open"</string> <string name="action_open">"Open"</string>
<string name="action_pause">"Pause"</string> <string name="action_pause">"Pause"</string>
<string name="action_pending">"Pending"</string> <string name="action_pending">"Pending"</string>
@@ -201,9 +204,12 @@
<string name="installer_root_available">"Root access available"</string> <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_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 &amp; all permissions are granted"</string>
<string name="installer_status_failure">"Installation failed"</string> <string name="installer_status_failure">"Installation failed"</string>
<string name="installer_status_failure_aborted">"Installation cancelled"</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_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_conflict">"Conflicting package exists"</string>
<string name="installer_status_failure_incompatible">"Incompatible app"</string> <string name="installer_status_failure_incompatible">"Incompatible app"</string>
<string name="installer_status_failure_invalid">"Invalid or corrupted APK"</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_games">"Games"</string>
<string name="title_installation">"Installation"</string> <string name="title_installation">"Installation"</string>
<string name="title_installed">"Installed"</string> <string name="title_installed">"Installed"</string>
<string name="title_installer">"App Installer"</string>
<string name="title_language">"Language"</string> <string name="title_language">"Language"</string>
<string name="title_library">"Library"</string> <string name="title_library">"Library"</string>
<string name="title_purchase_history">"Purchase history"</string> <string name="title_purchase_history">"Purchase history"</string>

View File

@@ -72,4 +72,28 @@
<style name="Aurora.BottomSheetDialog" parent="Theme.Design.BottomSheetDialog"> <style name="Aurora.BottomSheetDialog" parent="Theme.Design.BottomSheetDialog">
<item name="bottomSheetStyle">@style/Aurora.BottomSheetDialogStyle</item> <item name="bottomSheetStyle">@style/Aurora.BottomSheetDialogStyle</item>
</style> </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> </resources>