mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-06-13 10:14:42 -04:00
Merge branch 'dev'
This commit is contained in:
@@ -46,6 +46,12 @@
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_CREDENTIALS"
|
||||
android:maxSdkVersion="22" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
@@ -81,7 +87,8 @@
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ object Constants {
|
||||
const val SHARE_URL = "https://play.google.com/store/apps/details?id="
|
||||
|
||||
const val UPDATE_URL_STABLE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/updates.json"
|
||||
const val UPDATE_URL_NIGHTLY = "https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
|
||||
const val UPDATE_URL_NIGHTLY =
|
||||
"https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
|
||||
|
||||
const val NOTIFICATION_CHANNEL_EXPORT = "NOTIFICATION_CHANNEL_EXPORT"
|
||||
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALL"
|
||||
@@ -56,4 +57,6 @@ object Constants {
|
||||
const val PAGE_TYPE = "PAGE_TYPE"
|
||||
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"
|
||||
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
|
||||
|
||||
const val JSON_MIME_TYPE = "application/json"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
|
||||
fun PackageInfo.isValidApp(packageManager: PackageManager): Boolean {
|
||||
if (this.applicationInfo == null || this.packageName.isEmpty()) return false
|
||||
|
||||
// Most core AOSP system apps use their package name as label
|
||||
if (this.applicationInfo!!.loadLabel(packageManager).startsWith(this.packageName)) return false
|
||||
// Filter out core AOSP system apps
|
||||
if (this.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM != 0) {
|
||||
if (this.packageName.endsWith(".resources")) return false
|
||||
if (this.applicationInfo!!.loadLabel(packageManager).startsWith(this.packageName)) return false
|
||||
if (this.versionName?.endsWith("system image") == true) return false
|
||||
if (this.versionName?.endsWith("-initial") == true) return false
|
||||
if (this.versionName == Build.VERSION.RELEASE && PackageInfoCompat.getLongVersionCode(this) == Build.VERSION.SDK_INT.toLong()) return false
|
||||
}
|
||||
|
||||
return when {
|
||||
isQAndAbove -> {
|
||||
|
||||
@@ -82,7 +82,7 @@ class MainActivity : AppCompatActivity() {
|
||||
// Adjust root view's paddings for edgeToEdge display
|
||||
ViewCompat.setOnApplyWindowInsetsListener(B.root) { root, windowInsets ->
|
||||
val insets = windowInsets.getInsets(systemBars() or displayCutout() or ime())
|
||||
root.setPadding(0, insets.top, 0, 0)
|
||||
root.setPadding(insets.left, insets.top, insets.right, 0)
|
||||
windowInsets
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,3 @@ enum class AccountType {
|
||||
ANONYMOUS,
|
||||
GOOGLE
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
QUEUED,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
CANCELED,
|
||||
INSTALLING,
|
||||
}
|
||||
@@ -2,34 +2,61 @@ package com.aurora.store.data.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Parcelable
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.data.room.update.Update
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class MinimalApp(
|
||||
val packageName: String,
|
||||
val versionName: String,
|
||||
val versionCode: Int,
|
||||
val displayName: String
|
||||
val displayName: String,
|
||||
@IgnoredOnParcel
|
||||
val icon: Bitmap? = null
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromApp(app: App): MinimalApp {
|
||||
return MinimalApp(app.packageName, app.versionCode, app.displayName)
|
||||
return MinimalApp(
|
||||
app.packageName,
|
||||
app.versionName,
|
||||
app.versionCode,
|
||||
app.displayName
|
||||
)
|
||||
}
|
||||
|
||||
fun toApp(minimalApp: MinimalApp): App {
|
||||
return App(minimalApp.packageName).apply {
|
||||
versionName = minimalApp.versionName ?: ""
|
||||
versionCode = minimalApp.versionCode
|
||||
displayName = minimalApp.displayName
|
||||
}
|
||||
}
|
||||
|
||||
fun fromUpdate(update: Update): MinimalApp {
|
||||
return MinimalApp(update.packageName, update.versionCode, update.displayName)
|
||||
return MinimalApp(
|
||||
update.packageName,
|
||||
update.versionName,
|
||||
update.versionCode,
|
||||
update.displayName
|
||||
)
|
||||
}
|
||||
|
||||
fun fromPackageInfo(context: Context, packageInfo: PackageInfo): MinimalApp {
|
||||
return MinimalApp(
|
||||
packageInfo.packageName,
|
||||
packageInfo.versionName ?: "",
|
||||
PackageInfoCompat.getLongVersionCode(packageInfo).toInt(),
|
||||
packageInfo.applicationInfo!!.loadLabel(context.packageManager).toString()
|
||||
packageInfo.applicationInfo!!.loadLabel(context.packageManager).toString(),
|
||||
PackageUtil.getIconForPackage(context, packageInfo.packageName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.aurora.store.data.network
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.Algorithm
|
||||
import com.aurora.store.data.model.ProxyInfo
|
||||
@@ -60,16 +61,20 @@ object OkHttpClientModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOkHttpClientInstance(certPinner: CertificatePinner, proxy: Proxy?): OkHttpClient {
|
||||
return OkHttpClient().newBuilder()
|
||||
val okHttpClientBuilder = OkHttpClient().newBuilder()
|
||||
.proxy(proxy)
|
||||
.certificatePinner(certPinner)
|
||||
.connectTimeout(25, TimeUnit.SECONDS)
|
||||
.readTimeout(25, TimeUnit.SECONDS)
|
||||
.writeTimeout(25, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.build()
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
okHttpClientBuilder.certificatePinner(certPinner)
|
||||
}
|
||||
|
||||
return okHttpClientBuilder.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.aurora.store.data.room.favourite
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -16,6 +18,26 @@ data class Favourite(
|
||||
val mode: Mode
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
fun fromApp(app: App, mode: Mode): Favourite {
|
||||
return Favourite(
|
||||
packageName = app.packageName,
|
||||
displayName = app.displayName,
|
||||
iconURL = app.iconArtwork.url,
|
||||
added = System.currentTimeMillis(),
|
||||
mode = mode
|
||||
)
|
||||
}
|
||||
|
||||
fun Favourite.toApp(): App {
|
||||
return App(
|
||||
packageName = packageName,
|
||||
displayName = displayName,
|
||||
iconArtwork = Artwork(url = iconURL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
MANUAL,
|
||||
IMPORT
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.aurora.store.data.work
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
@@ -10,8 +16,16 @@ import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.data.model.AccountType
|
||||
import com.aurora.store.data.providers.AccountProvider
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Worker to refresh [AuthData] in background
|
||||
@@ -26,6 +40,8 @@ open class AuthWorker @AssistedInject constructor(
|
||||
|
||||
private val TAG = AuthWorker::class.java.simpleName
|
||||
|
||||
private val authToken: MutableSharedFlow<String?> = MutableSharedFlow(extraBufferCapacity = 1)
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
if (!AccountProvider.isLoggedIn(appContext)) {
|
||||
Log.i(TAG, "User has logged out!")
|
||||
@@ -42,14 +58,48 @@ open class AuthWorker @AssistedInject constructor(
|
||||
val accountType = AccountProvider.getAccountType(appContext)
|
||||
val authData = when (accountType) {
|
||||
AccountType.GOOGLE -> {
|
||||
authProvider.buildGoogleAuthData(
|
||||
AccountProvider.getLoginEmail(appContext)!!,
|
||||
AccountProvider.getLoginToken(appContext)!!.first,
|
||||
AccountProvider.getLoginToken(appContext)!!.second
|
||||
).getOrThrow()
|
||||
val email = AccountProvider.getLoginEmail(appContext)!!
|
||||
val token = AccountProvider.getLoginToken(appContext)!!.first
|
||||
val tokenType = AccountProvider.getLoginToken(appContext)!!.second
|
||||
|
||||
if (tokenType == AuthHelper.Token.AAS) {
|
||||
Log.i(TAG, "Refreshing AuthData for personal account")
|
||||
authProvider.buildGoogleAuthData(email, token, tokenType).getOrThrow()
|
||||
} else {
|
||||
/*
|
||||
* We are working with AuthToken here. The only scenario when we will have
|
||||
* AuthToken and Google login is when the user used microG to login into
|
||||
* Aurora Store. In this case, we use system's AccountManager to request credentials.
|
||||
*/
|
||||
Log.i(TAG, "Refreshing AuthData for personal account using AccountManager")
|
||||
AccountManager.get(appContext)
|
||||
.getAuthToken(
|
||||
Account(email, GOOGLE_ACCOUNT_TYPE),
|
||||
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
|
||||
bundleOf(
|
||||
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
|
||||
"overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
|
||||
),
|
||||
true,
|
||||
{
|
||||
authToken.tryEmit(it.result.getString(AccountManager.KEY_AUTHTOKEN))
|
||||
},
|
||||
Handler(Looper.getMainLooper())
|
||||
)
|
||||
runBlocking {
|
||||
authProvider.buildGoogleAuthData(
|
||||
email,
|
||||
authToken.take(1).first()!!,
|
||||
tokenType
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccountType.ANONYMOUS -> authProvider.buildAnonymousAuthData().getOrThrow()
|
||||
AccountType.ANONYMOUS -> {
|
||||
Log.i(TAG, "Refreshing AuthData for anonymous account")
|
||||
authProvider.buildAnonymousAuthData().getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
require(verifyAndSaveAuth(authData, accountType) != null)
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.gplayapi.helpers.SearchHelper
|
||||
import com.aurora.gplayapi.helpers.StreamHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
||||
@@ -152,4 +153,15 @@ object HelperModule {
|
||||
.using(httpClient)
|
||||
.with(spoofProvider.locale)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesWebAppDetailsHelperInstance(
|
||||
spoofProvider: SpoofProvider,
|
||||
httpClient: IHttpClient
|
||||
): WebAppDetailsHelper {
|
||||
return WebAppDetailsHelper()
|
||||
.using(httpClient)
|
||||
.with(spoofProvider.locale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,27 @@ import com.aurora.store.data.model.Algorithm
|
||||
import com.aurora.store.util.PackageUtil.getPackageInfo
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object CertUtil {
|
||||
|
||||
private val TAG = "CertUtil"
|
||||
|
||||
const val GOOGLE_ACCOUNT_TYPE = "com.google"
|
||||
const val GOOGLE_PLAY_AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/googleplay"
|
||||
const val GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"
|
||||
const val GOOGLE_PLAY_CERT =
|
||||
"MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK"
|
||||
|
||||
// Keep this list updated as & when new signatures are added.
|
||||
private val knownGMSSignatures = listOf(
|
||||
"bd32424203e0fb25f36b57e5aa356f9bdd1da998",
|
||||
"38918a453d07199354f8b19af05ec6562ced5788,",
|
||||
"2169eddb5fbb1fdf241c262681024692c4fc1ecb",
|
||||
"58e1c4133f7441ec3d2c270270a14802da47ba0e",
|
||||
"4f87463a1ae6f7d71b2c0b0658845790236dba42"
|
||||
)
|
||||
|
||||
fun isFDroidApp(context: Context, packageName: String): Boolean {
|
||||
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
|
||||
}
|
||||
@@ -84,6 +100,31 @@ object CertUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isGoogleGMS(context: Context, packageName: String): Boolean {
|
||||
return try {
|
||||
getX509Certificates(context, packageName).any { certificate ->
|
||||
val signatureHash = extractSHA1Fingerprint(certificate)
|
||||
|
||||
if (knownGMSSignatures.contains(signatureHash)) return true
|
||||
|
||||
// Follow heuristics to determine if the app is signed by Google, just to ensure we don't miss any.
|
||||
listOf(
|
||||
certificate.issuerX500Principal,
|
||||
certificate.subjectX500Principal
|
||||
).any {
|
||||
val map = parseX500Principal(it)
|
||||
map["O"] == "Google LLC" || map["O"] == "Google Inc."
|
||||
&& map["L"] == "Mountain View"
|
||||
&& map["ST"] == "California"
|
||||
&& map["C"] == "US"
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to check signing cert for $packageName")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInstalledByFDroid(context: Context, packageName: String): Boolean {
|
||||
val fdroidPackages = listOf(
|
||||
"org.fdroid.basic", "org.fdroid.fdroid", "org.fdroid.fdroid.privileged"
|
||||
@@ -120,4 +161,19 @@ object CertUtil {
|
||||
getPackageInfo(context, packageName, PackageManager.GET_SIGNATURES)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractSHA1Fingerprint(certificate: X509Certificate): String {
|
||||
val messageDigest = MessageDigest.getInstance(Algorithm.SHA1.value)
|
||||
messageDigest.update(certificate.encoded)
|
||||
return messageDigest.digest()
|
||||
.joinToString("") { byte -> String.format("%02x", byte) }
|
||||
.lowercase()
|
||||
}
|
||||
|
||||
private fun parseX500Principal(principal: X500Principal): Map<String, String> {
|
||||
return principal.name.split(",").associate {
|
||||
val (left, right) = it.split("=")
|
||||
left.trim() to right.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,13 @@ import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PackageInfoFlags
|
||||
import android.content.pm.SharedLibraryInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.aurora.extensions.isHuawei
|
||||
@@ -40,12 +42,17 @@ import com.aurora.extensions.isPAndAbove
|
||||
import com.aurora.extensions.isTAndAbove
|
||||
import com.aurora.extensions.isValidApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import java.util.Locale
|
||||
|
||||
object PackageUtil {
|
||||
|
||||
private const val TAG = "PackageUtil"
|
||||
|
||||
private const val PACKAGE_NAME_MICRO_G = "com.google.android.gms"
|
||||
private const val VERSION_CODE_MICRO_G = 240913402
|
||||
private const val VERSION_CODE_MICRO_G_HUAWEI = 240913007
|
||||
|
||||
fun getAllValidPackages(context: Context): List<PackageInfo> {
|
||||
val sharedLibs = context.packageManager.systemSharedLibraryNames ?: emptyArray()
|
||||
return context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||
@@ -57,6 +64,19 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasSupportedMicroG(context: Context): Boolean {
|
||||
val isGoogle = CertUtil.isGoogleGMS(context, PACKAGE_NAME_MICRO_G)
|
||||
|
||||
// Do not check for MicroG if Google Play Services is installed
|
||||
if (isGoogle) return false
|
||||
|
||||
return if (isHuawei) {
|
||||
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G_HUAWEI)
|
||||
} else {
|
||||
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G)
|
||||
}
|
||||
}
|
||||
|
||||
fun isInstalled(context: Context, packageName: String): Boolean {
|
||||
return try {
|
||||
getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
|
||||
@@ -218,6 +238,20 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconDrawableForPackage(context: Context, packageName: String): Drawable? {
|
||||
val placeholder = AppCompatResources.getDrawable(context, R.drawable.bg_placeholder)
|
||||
|
||||
return try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
|
||||
val applicationInfo = packageInfo.applicationInfo ?: return placeholder
|
||||
|
||||
applicationInfo.loadIcon(context.packageManager)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to get icon for package!", exception)
|
||||
placeholder
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllSharedLibraries(context: Context, flags: Int = 0): List<SharedLibraryInfo> {
|
||||
return if (isTAndAbove) {
|
||||
context.packageManager.getSharedLibraries(PackageInfoFlags.of(flags.toLong()))
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.State
|
||||
import com.aurora.store.databinding.ViewActionButtonBinding
|
||||
|
||||
class ActionButton : RelativeLayout {
|
||||
|
||||
private lateinit var binding: ViewActionButtonBinding
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
val view = inflate(context, R.layout.view_action_button, this)
|
||||
binding = ViewActionButtonBinding.bind(view)
|
||||
|
||||
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)
|
||||
|
||||
binding.btn.text = btnTxt
|
||||
binding.btn.setTextColor(stateColor)
|
||||
binding.img.setImageDrawable(ContextCompat.getDrawable(context, stateIcon))
|
||||
binding.img.imageTintList = ColorStateList.valueOf(stateColor)
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
fun setText(text: String) {
|
||||
binding.viewFlipper.displayedChild = 0
|
||||
binding.btn.text = text
|
||||
}
|
||||
|
||||
fun setText(text: Int) {
|
||||
binding.viewFlipper.displayedChild = 0
|
||||
binding.btn.text = ContextCompat.getString(context, text)
|
||||
}
|
||||
|
||||
fun setButtonState(enabled: Boolean = true) {
|
||||
binding.btn.isEnabled = enabled
|
||||
}
|
||||
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.PROGRESS -> 1
|
||||
State.COMPLETE -> 2
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (binding.viewFlipper.displayedChild != displayChild) {
|
||||
binding.viewFlipper.displayedChild = displayChild
|
||||
|
||||
if (displayChild == 2) updateState(State.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnClickListener(onClickListener: OnClickListener?) {
|
||||
binding.btn.setOnClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
package com.aurora.store.view.epoxy.views
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
@@ -31,30 +29,29 @@ import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.databinding.ViewPackageBinding
|
||||
import com.aurora.store.util.PackageUtil
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class PackageInfoView @JvmOverloads constructor(
|
||||
class InstalledAppView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewPackageBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode])
|
||||
fun packageInfo(packageInfo: PackageInfo) {
|
||||
val appInfo = packageInfo.applicationInfo!!
|
||||
binding.imgIcon.load(PackageUtil.getIconForPackage(context, appInfo.packageName)) {
|
||||
fun packageInfo(app: MinimalApp) {
|
||||
binding.imgIcon.load(app.icon) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(25F))
|
||||
}
|
||||
|
||||
binding.txtLine1.text = appInfo.loadLabel(context.packageManager)
|
||||
binding.txtLine2.text = appInfo.packageName
|
||||
binding.txtLine3.text = ("${packageInfo.versionName}.${PackageInfoCompat.getLongVersionCode(packageInfo)}")
|
||||
binding.txtLine1.text = app.displayName
|
||||
binding.txtLine2.text = app.packageName
|
||||
binding.txtLine3.text = ("${app.versionName} (${app.versionCode})")
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
@@ -42,10 +42,7 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.txtTitle.text = getString(R.string.title_about)
|
||||
binding.layoutToolbarAction.imgActionPrimary.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
// About Details
|
||||
binding.imgIcon.load(R.mipmap.ic_launcher)
|
||||
|
||||
@@ -47,10 +47,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.txtTitle.text = getString(R.string.title_account_manager)
|
||||
binding.layoutToolbarAction.imgActionPrimary.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
// Chips
|
||||
view.context.apply {
|
||||
|
||||
@@ -19,36 +19,47 @@
|
||||
|
||||
package com.aurora.store.view.ui.all
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.PackageInfoViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.all.InstalledViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
|
||||
private val viewModel: InstalledViewModel by viewModels()
|
||||
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportInstalledApps(it) else toast(R.string.toast_fav_export_failed)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.packages.collect {
|
||||
viewModel.apps.collect {
|
||||
updateController(it)
|
||||
}
|
||||
}
|
||||
@@ -67,38 +78,47 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.apply {
|
||||
imgActionPrimary.visibility = View.VISIBLE
|
||||
imgActionSecondary.visibility = View.GONE
|
||||
|
||||
imgActionPrimary.setOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
inputSearch.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
updateController(viewModel.packages.value)
|
||||
} else {
|
||||
val filteredPackages = viewModel.packages.value?.filter {
|
||||
it.applicationInfo!!.loadLabel(requireContext().packageManager)
|
||||
.contains(s, true) || it.packageName.contains(s, true)
|
||||
}
|
||||
updateController(filteredPackages)
|
||||
binding.toolbar.apply {
|
||||
inflateMenu(R.menu.menu_import_export)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_apps_${Calendar.getInstance().time.time}.json"
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
else -> false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
updateController(viewModel.apps.value)
|
||||
} else {
|
||||
val filteredPackages = viewModel.apps.value?.filter {
|
||||
it.displayName.contains(s, true) || it.packageName.contains(s, true)
|
||||
}
|
||||
updateController(filteredPackages)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateController(packages: List<PackageInfo>?) {
|
||||
private fun updateController(packages: List<App>?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (packages == null) {
|
||||
@@ -116,12 +136,21 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
)
|
||||
packages.forEach { app ->
|
||||
add(
|
||||
PackageInfoViewModel_()
|
||||
AppListViewModel_()
|
||||
.id(app.packageName.hashCode())
|
||||
.packageInfo(app)
|
||||
.click { _ -> openDetailsFragment(app.packageName) }
|
||||
.app(app)
|
||||
.click { _ ->
|
||||
openDetailsFragment(
|
||||
app.packageName,
|
||||
app
|
||||
)
|
||||
}
|
||||
.longClick { _ ->
|
||||
openAppMenuSheet(MinimalApp.fromPackageInfo(requireContext(), app))
|
||||
openAppMenuSheet(
|
||||
MinimalApp.fromApp(
|
||||
app
|
||||
)
|
||||
)
|
||||
false
|
||||
}
|
||||
)
|
||||
@@ -130,4 +159,8 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportInstalledApps(uri: Uri) {
|
||||
viewModel.exportApps(requireContext(), uri)
|
||||
toast(R.string.toast_fav_export_success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,19 @@
|
||||
package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
||||
import com.aurora.store.view.epoxy.views.BlackListViewModel_
|
||||
@@ -35,12 +40,22 @@ import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.all.BlacklistViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
|
||||
private val viewModel: BlacklistViewModel by viewModels()
|
||||
|
||||
private val startForDocumentImport =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it != null) importBlacklist(it) else toast(R.string.toast_black_import_failed)
|
||||
}
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportBlacklist(it) else toast(R.string.toast_black_export_failed)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -51,37 +66,62 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.apply {
|
||||
imgActionPrimary.visibility = View.VISIBLE
|
||||
imgActionSecondary.visibility = View.GONE
|
||||
|
||||
imgActionPrimary.setOnClickListener {
|
||||
binding.toolbar.apply {
|
||||
inflateMenu(R.menu.menu_blacklist)
|
||||
setNavigationOnClickListener {
|
||||
viewModel.blacklistProvider.blacklist = viewModel.selected
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_import -> {
|
||||
startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
}
|
||||
|
||||
inputSearch.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
updateController(viewModel.packages.value)
|
||||
} else {
|
||||
val filteredPackages = viewModel.packages.value?.filter {
|
||||
it.applicationInfo!!.loadLabel(requireContext().packageManager)
|
||||
.contains(s, true) || it.packageName.contains(s, true)
|
||||
}
|
||||
updateController(filteredPackages)
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_apps_${Calendar.getInstance().time.time}.json"
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_select_all -> {
|
||||
viewModel.selectAll()
|
||||
binding.recycler.requestModelBuild()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_remove_all -> {
|
||||
viewModel.removeAll()
|
||||
binding.recycler.requestModelBuild()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {}
|
||||
})
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
updateController(viewModel.packages.value)
|
||||
} else {
|
||||
val filteredPackages = viewModel.packages.value?.filter {
|
||||
it.applicationInfo!!.loadLabel(requireContext().packageManager)
|
||||
.contains(s, true) || it.packageName.contains(s, true)
|
||||
}
|
||||
updateController(filteredPackages)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -125,4 +165,15 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun importBlacklist(uri: Uri) {
|
||||
viewModel.importBlacklist(requireContext(), uri)
|
||||
binding.recycler.requestModelBuild()
|
||||
toast(R.string.toast_black_import_success)
|
||||
}
|
||||
|
||||
private fun exportBlacklist(uri: Uri) {
|
||||
viewModel.exportBlacklist(requireContext(), uri)
|
||||
toast(R.string.toast_black_export_success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
@@ -30,7 +29,6 @@ import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.gplayapi.utils.CategoryUtil
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.model.ViewState.Loading.getDataAs
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
@@ -57,9 +55,8 @@ class CategoryBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>()
|
||||
val genericCarouselController = CategoryCarouselController(this)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.toolbar.apply {
|
||||
binding.toolbar.apply {
|
||||
title = args.title
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,11 @@ package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.groups.CarouselHorizontalModel_
|
||||
@@ -51,9 +49,8 @@ class ExpandedStreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBind
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.toolbar.apply {
|
||||
binding.toolbar.apply {
|
||||
title = args.title
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
@@ -71,7 +68,7 @@ class ExpandedStreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBind
|
||||
|
||||
private fun updateTitle(streamCluster: StreamCluster) {
|
||||
if (streamCluster.clusterTitle.isNotEmpty())
|
||||
binding.layoutToolbarNative.toolbar.title = streamCluster.clusterTitle
|
||||
binding.toolbar.title = streamCluster.clusterTitle
|
||||
}
|
||||
|
||||
private fun attachRecycler() {
|
||||
|
||||
@@ -26,9 +26,11 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.Favourite.Companion.toApp
|
||||
import com.aurora.store.databinding.FragmentFavouriteBinding
|
||||
import com.aurora.store.view.epoxy.views.FavouriteViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||
@@ -42,14 +44,13 @@ import java.util.Calendar
|
||||
class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
private val viewModel: FavouriteViewModel by viewModels()
|
||||
|
||||
private val mimeType = "application/json"
|
||||
private val startForDocumentImport =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it != null) importDeviceConfig(it) else toast(R.string.toast_fav_import_failed)
|
||||
if (it != null) importFavourites(it) else toast(R.string.toast_fav_import_failed)
|
||||
}
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
|
||||
if (it != null) exportDeviceConfig(it) else toast(R.string.toast_fav_export_failed)
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -65,7 +66,7 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
binding.toolbar.apply {
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_import -> startForDocumentImport.launch(arrayOf(mimeType))
|
||||
R.id.action_import -> startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_favourites_${Calendar.getInstance().time.time}.json"
|
||||
@@ -103,7 +104,7 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
FavouriteViewModel_()
|
||||
.id(it.packageName.hashCode())
|
||||
.favourite(it)
|
||||
.onClick { _ -> openDetailsFragment(it.packageName) }
|
||||
.onClick { _ -> openDetailsFragment(it.packageName, it.toApp()) }
|
||||
.onFavourite { _ -> viewModel.removeFavourite(it.packageName) }
|
||||
)
|
||||
}
|
||||
@@ -111,13 +112,13 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun importDeviceConfig(uri: Uri) {
|
||||
private fun importFavourites(uri: Uri) {
|
||||
viewModel.importFavourites(requireContext(), uri)
|
||||
binding.recycler.requestModelBuild()
|
||||
toast(R.string.toast_fav_import_success)
|
||||
}
|
||||
|
||||
private fun exportDeviceConfig(uri: Uri) {
|
||||
private fun exportFavourites(uri: Uri) {
|
||||
viewModel.exportFavourites(requireContext(), uri)
|
||||
toast(R.string.toast_fav_export_success)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,10 @@ package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
@@ -48,9 +46,8 @@ class StreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
streamCluster = args.cluster
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.toolbar.apply {
|
||||
binding.toolbar.apply {
|
||||
title = streamCluster.clusterTitle
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
|
||||
@@ -20,33 +20,35 @@
|
||||
|
||||
package com.aurora.store.view.ui.details
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import coil3.asDrawable
|
||||
import coil3.load
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.error
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.CircleCropTransformation
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.aurora.Constants
|
||||
import com.aurora.Constants.EXODUS_SUBMIT_PAGE
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.extensions.requiresObbDir
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.extensions.share
|
||||
@@ -66,7 +68,6 @@ import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.model.PermissionType
|
||||
import com.aurora.store.data.model.State
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.model.ViewState.Loading.getDataAs
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
@@ -75,6 +76,8 @@ import com.aurora.store.util.CertUtil
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
import com.aurora.store.util.ShortcutManagerUtil
|
||||
import com.aurora.store.view.custom.RatingView
|
||||
import com.aurora.store.view.epoxy.controller.DetailsCarouselController
|
||||
@@ -85,11 +88,10 @@ import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.details.DetailsClusterViewModel
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
@@ -106,46 +108,35 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
@Inject
|
||||
lateinit var authProvider: AuthProvider
|
||||
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>
|
||||
private lateinit var app: App
|
||||
private lateinit var iconDrawable: Drawable
|
||||
|
||||
private var streamBundle: StreamBundle? = StreamBundle()
|
||||
|
||||
private val isExternal get() = activity?.intent?.action != Intent.ACTION_MAIN
|
||||
|
||||
private var downloadStatus = DownloadStatus.UNAVAILABLE
|
||||
private var isUpdatable: Boolean = false
|
||||
private var uninstallActionEnabled = false
|
||||
private val isExtendedUpdateEnabled: Boolean
|
||||
get() = Preferences.getBoolean(requireContext(), PREFERENCE_UPDATES_EXTENDED)
|
||||
private val showSimilarApps: Boolean
|
||||
get() = Preferences.getBoolean(requireContext(), PREFERENCE_SIMILAR)
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is InstallerEvent.Installed -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
binding.layoutDetailsToolbar.toolbar.menu.apply {
|
||||
findItem(R.id.action_home_screen)?.isVisible =
|
||||
ShortcutManagerUtil.canPinShortcut(requireContext(), app.packageName)
|
||||
findItem(R.id.action_uninstall)?.isVisible = true
|
||||
findItem(R.id.menu_app_settings)?.isVisible = true
|
||||
}
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
}
|
||||
|
||||
is InstallerEvent.Uninstalled -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
binding.layoutDetailsToolbar.toolbar.menu.apply {
|
||||
findItem(R.id.action_home_screen)?.isVisible = false
|
||||
findItem(R.id.action_uninstall)?.isVisible = false
|
||||
findItem(R.id.menu_app_settings)?.isVisible = false
|
||||
}
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
}
|
||||
|
||||
is BusEvent.ManualDownload -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
app.versionCode = event.versionCode
|
||||
purchase()
|
||||
purchase(app.copy(versionCode = event.versionCode))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +155,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
|
||||
is InstallerEvent.Installing -> {
|
||||
if (event.packageName == app.packageName) {
|
||||
attachActions()
|
||||
updateActionState(State.INSTALLING)
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,35 +168,17 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Adjust margins for edgeToEdge display
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.layoutDetailsDev.root) { v, w ->
|
||||
val insets = w.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin += insets.bottom
|
||||
}
|
||||
WindowInsetsCompat.CONSUMED
|
||||
app = args.app ?: App(args.packageName)
|
||||
app.apply {
|
||||
// Check whether app is installed or not
|
||||
isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
}
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.layoutDetailsInstall.viewFlipper) { v, w ->
|
||||
val insets = w.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin += insets.bottom
|
||||
}
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
if (args.app != null) {
|
||||
app = args.app!!
|
||||
inflatePartialApp()
|
||||
} else {
|
||||
app = App(args.packageName)
|
||||
}
|
||||
// Show the basic app details, while the rest of the data is being fetched
|
||||
updateAppHeader(app, false)
|
||||
|
||||
// Toolbar
|
||||
attachToolbar()
|
||||
|
||||
// Check whether app is installed or not
|
||||
app.isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
updateToolbar(app)
|
||||
|
||||
// App Details
|
||||
viewModel.fetchAppDetails(app.packageName)
|
||||
@@ -215,38 +187,55 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
viewModel.app.collect {
|
||||
if (it.packageName.isNotBlank()) {
|
||||
app = it
|
||||
inflatePartialApp() // Re-inflate the app details, as web data may vary.
|
||||
inflateExtraDetails(app)
|
||||
|
||||
// App User Review
|
||||
// We can not fetch it outside of this block, as we need the testing program status
|
||||
if (!authProvider.isAnonymous && app.isInstalled) {
|
||||
viewModel.fetchUserAppReview(app)
|
||||
}
|
||||
|
||||
updateToolbar(app)
|
||||
updateAppHeader(app) // Re-inflate the app details, as web data may vary.
|
||||
updateExtraDetails(app)
|
||||
|
||||
if (app.versionCode == 0) {
|
||||
warnAppUnavailable(app)
|
||||
}
|
||||
|
||||
// Fetch App Reviews
|
||||
viewModel.fetchAppReviews(app.packageName)
|
||||
|
||||
// Fetch Data Safety Report
|
||||
viewModel.fetchAppDataSafetyReport(app.packageName)
|
||||
|
||||
// Fetch Exodus Privacy Report
|
||||
viewModel.fetchAppReport(app.packageName)
|
||||
} else {
|
||||
toast("Failed to fetch app details")
|
||||
toast(getString(R.string.status_unavailable))
|
||||
// TODO: Redirect to App Unavailable Fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.downloadsList
|
||||
.filter { list -> list.any { it.packageName == app.packageName } }
|
||||
.collectLatest { downloadsList ->
|
||||
val download = downloadsList.find { it.packageName == app.packageName }
|
||||
download?.let {
|
||||
downloadStatus = it.downloadStatus
|
||||
|
||||
if (it.isFinished) flip(0) else flip(1)
|
||||
when (it.downloadStatus) {
|
||||
DownloadStatus.QUEUED -> {
|
||||
updateProgress(it.progress)
|
||||
}
|
||||
|
||||
DownloadStatus.DOWNLOADING -> {
|
||||
updateProgress(it.progress, it.speed, it.timeRemaining)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
viewModel.download.filterNotNull().onEach {
|
||||
when (it.downloadStatus) {
|
||||
DownloadStatus.QUEUED,
|
||||
DownloadStatus.DOWNLOADING -> {
|
||||
updateProgress(it.progress)
|
||||
binding.layoutDetailsApp.btnPrimaryAction.apply {
|
||||
isEnabled = false
|
||||
text = getString(R.string.action_open)
|
||||
setOnClickListener { openApp() }
|
||||
}
|
||||
binding.layoutDetailsApp.btnSecondaryAction.apply {
|
||||
text = getString(R.string.action_cancel)
|
||||
setOnClickListener { viewModel.cancelDownload(app) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> checkAndSetupInstall()
|
||||
}
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
// Reviews
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
@@ -261,12 +250,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.userReview.collect {
|
||||
if (it.commentId.isNotEmpty()) {
|
||||
binding.layoutDetailsReview.userStars.rating = it.rating.toFloat()
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.toast_rated_success),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
runOnUiThread { updateUserReview(it) }
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
@@ -350,12 +334,10 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.favourite.collect {
|
||||
if (it) {
|
||||
binding.layoutDetailsToolbar.toolbar.menu
|
||||
?.findItem(R.id.action_favourite)
|
||||
binding.toolbar.menu?.findItem(R.id.action_favourite)
|
||||
?.setIcon(R.drawable.ic_favorite_checked)
|
||||
} else {
|
||||
binding.layoutDetailsToolbar.toolbar.menu
|
||||
?.findItem(R.id.action_favourite)
|
||||
binding.toolbar.menu?.findItem(R.id.action_favourite)
|
||||
?.setIcon(R.drawable.ic_favorite_unchecked)
|
||||
}
|
||||
}
|
||||
@@ -368,12 +350,6 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
binding.layoutDetailsInstall.progressDownload.clipToOutline = true
|
||||
binding.layoutDetailsInstall.imgCancel.setOnClickListener {
|
||||
viewModel.cancelDownload(app)
|
||||
if (downloadStatus != DownloadStatus.DOWNLOADING) flip(0)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.events.busEvent.collect { onEvent(it) }
|
||||
}
|
||||
@@ -387,15 +363,11 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
private fun attachActions() {
|
||||
flip(0)
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
private fun attachToolbar() {
|
||||
binding.layoutDetailsToolbar.toolbar.apply {
|
||||
private fun updateToolbar(app: App) {
|
||||
binding.toolbar.apply {
|
||||
elevation = 0f
|
||||
navigationIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_arrow_back)
|
||||
|
||||
setNavigationOnClickListener {
|
||||
if (isExternal) {
|
||||
activity?.finish()
|
||||
@@ -404,8 +376,23 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
inflateMenu(R.menu.menu_details)
|
||||
if (menu.size() == 0) {
|
||||
// Inflate Menu only if it is not already inflated
|
||||
inflateMenu(R.menu.menu_details)
|
||||
}
|
||||
|
||||
// Adjust Menu Items
|
||||
menu.let {
|
||||
it.findItem(R.id.action_home_screen)?.isVisible =
|
||||
app.isInstalled && ShortcutManagerUtil.canPinShortcut(
|
||||
requireContext(),
|
||||
app.packageName
|
||||
)
|
||||
it.findItem(R.id.action_uninstall)?.isVisible = app.isInstalled
|
||||
it.findItem(R.id.menu_app_settings)?.isVisible = app.isInstalled
|
||||
}
|
||||
|
||||
// Set Menu Item Clicks
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_home_screen -> {
|
||||
@@ -451,88 +438,56 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
requireContext().browse("${Constants.SHARE_URL}${app.packageName}")
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
if (::app.isInitialized) {
|
||||
app.isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
|
||||
menu?.findItem(R.id.action_home_screen)?.isVisible =
|
||||
app.isInstalled && ShortcutManagerUtil.canPinShortcut(
|
||||
requireContext(),
|
||||
app.packageName
|
||||
)
|
||||
|
||||
menu?.findItem(R.id.action_uninstall)?.isVisible = app.isInstalled
|
||||
menu?.findItem(R.id.menu_app_settings)?.isVisible = app.isInstalled
|
||||
uninstallActionEnabled = app.isInstalled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachHeader() {
|
||||
private fun updateAppHeader(app: App, isFullApp: Boolean = true) {
|
||||
binding.layoutDetailsApp.apply {
|
||||
val fallbackDrawable = if (app.iconArtwork.url.isNotBlank())
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.bg_placeholder)
|
||||
else
|
||||
PackageUtil.getIconDrawableForPackage(requireContext(), app.packageName)
|
||||
|
||||
imgIcon.load(app.iconArtwork.url) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
error(fallbackDrawable)
|
||||
transformations(RoundedCornersTransformation(32F))
|
||||
listener { _, result ->
|
||||
result.image.asDrawable(resources).let { iconDrawable = it }
|
||||
}
|
||||
}
|
||||
|
||||
packageName.text = app.packageName
|
||||
txtLine1.text = app.displayName
|
||||
txtLine2.text = app.developerName
|
||||
txtLine3.text = ("${app.versionName} (${app.versionCode})")
|
||||
|
||||
txtLine2.setOnClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections
|
||||
.actionAppDetailsFragmentToDevAppsFragment(app.developerName)
|
||||
)
|
||||
}
|
||||
txtLine3.text = ("${app.versionName} (${app.versionCode})")
|
||||
packageName.text = app.packageName
|
||||
|
||||
val tags = mutableListOf<String>()
|
||||
if (app.isFree)
|
||||
tags.add(getString(R.string.details_free))
|
||||
else
|
||||
tags.add(getString(R.string.details_paid))
|
||||
// Do not show tags for web apps or unknown apps
|
||||
if (isFullApp) {
|
||||
val tags = mutableSetOf<String>().apply {
|
||||
if (app.isFree) {
|
||||
add(getString(R.string.details_free))
|
||||
} else {
|
||||
add(getString(R.string.details_paid))
|
||||
}
|
||||
|
||||
if (app.containsAds)
|
||||
tags.add(getString(R.string.details_contains_ads))
|
||||
else
|
||||
tags.add(getString(R.string.details_no_ads))
|
||||
|
||||
txtLine4.text = tags.joinToString(separator = " • ")
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachBottomSheet() {
|
||||
binding.layoutDetailsInstall.apply {
|
||||
viewFlipper.setInAnimation(requireContext(), R.anim.fade_in)
|
||||
viewFlipper.setOutAnimation(requireContext(), R.anim.fade_out)
|
||||
}
|
||||
|
||||
bottomSheetBehavior = BottomSheetBehavior.from(binding.layoutDetailsInstall.bottomSheet)
|
||||
bottomSheetBehavior.isDraggable = false
|
||||
|
||||
bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetCallback() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetBehavior.setDraggable(true)
|
||||
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetBehavior.isDraggable = false
|
||||
if (app.containsAds) {
|
||||
add(getString(R.string.details_contains_ads))
|
||||
} else {
|
||||
add(getString(R.string.details_no_ads))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateActionState(state: State) {
|
||||
runOnUiThread {
|
||||
binding.layoutDetailsInstall.btnDownload.apply {
|
||||
updateState(state)
|
||||
if (state == State.INSTALLING) {
|
||||
setButtonState(false)
|
||||
setText(R.string.action_installing)
|
||||
}
|
||||
txtLine4.text = tags.joinToString(separator = " • ")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,32 +503,15 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun startDownload() {
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.DOWNLOADING -> {
|
||||
flip(1)
|
||||
toast("Already downloading")
|
||||
}
|
||||
|
||||
else -> {
|
||||
flip(1)
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun purchase() {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
updateActionState(State.PROGRESS)
|
||||
|
||||
private fun purchase(app: App) {
|
||||
if (app.fileList.requiresObbDir()) {
|
||||
if (permissionProvider.isGranted(PermissionType.STORAGE_MANAGER)) {
|
||||
viewModel.download(app)
|
||||
} else {
|
||||
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
|
||||
if (it) viewModel.download(app) else flip(0)
|
||||
if (it) viewModel.download(app) else {
|
||||
// TODO: Ask for permission again or redirect to Permission Manager
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -581,149 +519,185 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(progress: Int, speed: Long = -1, timeRemaining: Long = -1) {
|
||||
runOnUiThread {
|
||||
if (progress == 100) {
|
||||
binding.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||
return@runOnUiThread
|
||||
}
|
||||
private fun updateProgress(progress: Int) {
|
||||
// No need to update progress if it is already 100% / completed
|
||||
transformIcon(progress != 100)
|
||||
if (progress == 100) return
|
||||
|
||||
binding.layoutDetailsInstall.apply {
|
||||
txtProgressPercent.text = ("${progress}%")
|
||||
progressDownload.apply {
|
||||
this.progress = progress
|
||||
isIndeterminate = progress < 1
|
||||
}
|
||||
txtEta.text = CommonUtil.getETAString(requireContext(), timeRemaining)
|
||||
txtSpeed.text = CommonUtil.getDownloadSpeedString(requireContext(), speed)
|
||||
binding.layoutDetailsApp.apply {
|
||||
progressDownload.progress = progress
|
||||
progressDownload.isIndeterminate = progress < 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformIcon(ongoing: Boolean = false) {
|
||||
if (::iconDrawable.isInitialized.not()) return
|
||||
|
||||
val imgIcon = binding.layoutDetailsApp.imgIcon
|
||||
val progressDownload = binding.layoutDetailsApp.progressDownload
|
||||
|
||||
// Avoids flickering when the download is in progress
|
||||
if (progressDownload.isShown && ongoing) return
|
||||
if (!progressDownload.isShown && !ongoing) return
|
||||
|
||||
binding.layoutDetailsApp.progressDownload.isVisible = ongoing
|
||||
|
||||
val scaleFactor = if (ongoing) 0.75f else 1f
|
||||
val scale = listOf(
|
||||
ObjectAnimator.ofFloat(imgIcon, "scaleX", scaleFactor),
|
||||
ObjectAnimator.ofFloat(imgIcon, "scaleY", scaleFactor)
|
||||
)
|
||||
|
||||
scale.forEach { animation ->
|
||||
animation.apply {
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
duration = 250
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
imgIcon.load(iconDrawable) {
|
||||
transformations(
|
||||
if (ongoing) {
|
||||
CircleCropTransformation()
|
||||
} else {
|
||||
RoundedCornersTransformation(8.px.toFloat())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndSetupInstall() {
|
||||
app.isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
|
||||
runOnUiThread {
|
||||
binding.layoutDetailsInstall.btnDownload.let { btn ->
|
||||
btn.setButtonState(true)
|
||||
if (app.isInstalled) {
|
||||
val isExtendedUpdateEnabled = Preferences.getBoolean(
|
||||
requireContext(), Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
)
|
||||
val needsExtendedUpdate = !app.certificateSetList.any {
|
||||
it.certificateSet in CertUtil.getEncodedCertificateHashes(
|
||||
requireContext(), app.packageName
|
||||
)
|
||||
}
|
||||
isUpdatable = PackageUtil.isUpdatable(
|
||||
requireContext(),
|
||||
app.packageName,
|
||||
app.versionCode.toLong()
|
||||
)
|
||||
// Setup primary and secondary action buttons
|
||||
binding.layoutDetailsApp.btnPrimaryAction.isEnabled = true
|
||||
binding.layoutDetailsApp.btnPrimaryAction.isEnabled = true
|
||||
|
||||
val installedVersion =
|
||||
PackageUtil.getInstalledVersion(requireContext(), app.packageName)
|
||||
if (app.isInstalled) {
|
||||
val isUpdatable = PackageUtil.isUpdatable(requireContext(), app.packageName, app.versionCode.toLong())
|
||||
val hasValidCert = app.certificateSetList.any {
|
||||
it.certificateSet in CertUtil.getEncodedCertificateHashes(requireContext(), app.packageName)
|
||||
}
|
||||
|
||||
if (isUpdatable && !needsExtendedUpdate || isUpdatable && isExtendedUpdateEnabled) {
|
||||
binding.layoutDetailsApp.txtLine3.text =
|
||||
("$installedVersion ➔ ${app.versionName} (${app.versionCode})")
|
||||
btn.setText(R.string.action_update)
|
||||
btn.addOnClickListener {
|
||||
if (app.versionCode == 0) {
|
||||
toast(R.string.toast_app_unavailable)
|
||||
} else {
|
||||
startDownload()
|
||||
}
|
||||
if ((isUpdatable && hasValidCert) || (isUpdatable && isExtendedUpdateEnabled)) {
|
||||
binding.layoutDetailsApp.btnPrimaryAction.apply {
|
||||
text = getString(R.string.action_update)
|
||||
setOnClickListener {
|
||||
if (app.versionCode == 0) {
|
||||
toast(R.string.toast_app_unavailable)
|
||||
setText(R.string.status_unavailable)
|
||||
} else {
|
||||
purchase(app)
|
||||
}
|
||||
} else {
|
||||
binding.layoutDetailsApp.txtLine3.text = installedVersion
|
||||
btn.setText(R.string.action_open)
|
||||
btn.addOnClickListener { openApp() }
|
||||
}
|
||||
if (!uninstallActionEnabled) {
|
||||
binding.layoutDetailsToolbar.toolbar.invalidateMenu()
|
||||
}
|
||||
} else {
|
||||
binding.layoutDetailsApp.apply {
|
||||
txtLine3.text = PackageUtil.getInstalledVersion(requireContext(), app.packageName)
|
||||
btnPrimaryAction.apply {
|
||||
setText(R.string.action_open)
|
||||
setOnClickListener { openApp() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.layoutDetailsApp.btnSecondaryAction.apply {
|
||||
text = getString(R.string.action_uninstall)
|
||||
setOnClickListener {
|
||||
AppInstaller.uninstall(requireContext(), app.packageName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (app.isFree) {
|
||||
binding.layoutDetailsApp.btnPrimaryAction.setText(R.string.action_install)
|
||||
} else {
|
||||
binding.layoutDetailsApp.btnPrimaryAction.text = app.price
|
||||
}
|
||||
|
||||
binding.layoutDetailsApp.btnPrimaryAction.setOnClickListener {
|
||||
if (authProvider.isAnonymous && !app.isFree) {
|
||||
toast(R.string.toast_purchase_blocked)
|
||||
return@setOnClickListener
|
||||
} else if (app.versionCode == 0) {
|
||||
toast(R.string.toast_app_unavailable)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (!permissionProvider.isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) {
|
||||
permissionProvider.request(PermissionType.INSTALL_UNKNOWN_APPS) {
|
||||
if (it) {
|
||||
purchase(app)
|
||||
} else {
|
||||
toast(R.string.permissions_denied)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (downloadStatus in DownloadStatus.running) {
|
||||
flip(1)
|
||||
} else if (app.isFree) {
|
||||
btn.setText(R.string.action_install)
|
||||
} else {
|
||||
btn.setText(app.price)
|
||||
}
|
||||
purchase(app)
|
||||
}
|
||||
}
|
||||
|
||||
btn.addOnClickListener {
|
||||
if (!permissionProvider.isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) {
|
||||
permissionProvider.request(PermissionType.INSTALL_UNKNOWN_APPS) {
|
||||
if (it) {
|
||||
btn.setText(R.string.download_metadata)
|
||||
startDownload()
|
||||
}
|
||||
}
|
||||
} else if (authProvider.isAnonymous && !app.isFree) {
|
||||
toast(R.string.toast_purchase_blocked)
|
||||
} else if (app.versionCode == 0) {
|
||||
toast(R.string.toast_app_unavailable)
|
||||
} else {
|
||||
btn.setText(R.string.download_metadata)
|
||||
startDownload()
|
||||
}
|
||||
}
|
||||
|
||||
if (uninstallActionEnabled) {
|
||||
binding.layoutDetailsToolbar.toolbar.invalidateMenu()
|
||||
}
|
||||
binding.layoutDetailsApp.btnSecondaryAction.apply {
|
||||
text = getString(R.string.title_manual_download)
|
||||
setOnClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections
|
||||
.actionAppDetailsFragmentToManualDownloadSheet(app)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun flip(nextView: Int) {
|
||||
runOnUiThread {
|
||||
val displayChild = binding.layoutDetailsInstall.viewFlipper.displayedChild
|
||||
if (displayChild != nextView) {
|
||||
binding.layoutDetailsInstall.viewFlipper.displayedChild = nextView
|
||||
if (nextView == 0) checkAndSetupInstall()
|
||||
// Lay out the toolbar again
|
||||
binding.toolbar.invalidateMenu()
|
||||
|
||||
if (app.isInstalled) {
|
||||
binding.toolbar.menu.apply {
|
||||
findItem(R.id.action_home_screen)?.isVisible =
|
||||
ShortcutManagerUtil.canPinShortcut(requireContext(), app.packageName)
|
||||
findItem(R.id.action_uninstall)?.isVisible = true
|
||||
findItem(R.id.menu_app_settings)?.isVisible = true
|
||||
}
|
||||
} else {
|
||||
binding.toolbar.menu.apply {
|
||||
findItem(R.id.action_home_screen)?.isVisible = false
|
||||
findItem(R.id.action_uninstall)?.isVisible = false
|
||||
findItem(R.id.menu_app_settings)?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
// Restore icon and progress
|
||||
updateProgress(100)
|
||||
}
|
||||
|
||||
private fun inflatePartialApp() {
|
||||
if (::app.isInitialized) {
|
||||
attachHeader()
|
||||
attachBottomSheet()
|
||||
attachActions()
|
||||
}
|
||||
}
|
||||
private fun updateExtraDetails(app: App) {
|
||||
binding.viewFlipper.displayedChild = 1
|
||||
|
||||
private fun inflateExtraDetails(app: App?) {
|
||||
app?.let {
|
||||
binding.viewFlipper.displayedChild = 1
|
||||
inflateAppDescription(app)
|
||||
inflateAppRatingAndReviews(app)
|
||||
inflateAppDevInfo(app)
|
||||
inflateAppDataSafety(app)
|
||||
inflateAppPrivacy(app)
|
||||
inflateAppPermission(app)
|
||||
updateAppDescription(app)
|
||||
updateAppRatingAndReviews(app)
|
||||
updateAppDevInfo(app)
|
||||
updateAppPermission(app)
|
||||
|
||||
if (!authProvider.isAnonymous) {
|
||||
app.testingProgram?.let {
|
||||
if (it.isAvailable && it.isSubscribed) {
|
||||
binding.layoutDetailsApp.txtLine1.text = it.displayName
|
||||
}
|
||||
// Allow users to handle beta subscriptions, if logged in by own account.
|
||||
if (!authProvider.isAnonymous) {
|
||||
// Update app name to the testing program name, if subscribed
|
||||
app.testingProgram?.let {
|
||||
if (it.isAvailable && it.isSubscribed) {
|
||||
binding.layoutDetailsApp.txtLine1.text = it.displayName
|
||||
}
|
||||
|
||||
inflateBetaSubscription(app)
|
||||
}
|
||||
|
||||
if (Preferences.getBoolean(requireContext(), Preferences.PREFERENCE_SIMILAR)) {
|
||||
inflateAppStream(app)
|
||||
}
|
||||
updateBetaSubscription(app)
|
||||
}
|
||||
|
||||
if (showSimilarApps) {
|
||||
updateAppStream(app)
|
||||
}
|
||||
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
private fun inflateAppDescription(app: App) {
|
||||
private fun updateAppDescription(app: App) {
|
||||
binding.layoutDetailDescription.apply {
|
||||
val installs = CommonUtil.addDiPrefix(app.installs)
|
||||
|
||||
@@ -780,10 +754,16 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateAppRatingAndReviews(app: App) {
|
||||
private fun updateAppRatingAndReviews(app: App) {
|
||||
binding.layoutDetailsReview.apply {
|
||||
averageRating.text = app.rating.average.toString()
|
||||
txtReviewCount.text = app.rating.abbreviatedLabel
|
||||
headerRatingReviews.addClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToDetailsReviewFragment(
|
||||
app.displayName,
|
||||
app.packageName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var totalStars = 0L
|
||||
totalStars += app.rating.oneStar
|
||||
@@ -803,42 +783,34 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
|
||||
averageRating.text = String.format(Locale.getDefault(), "%.1f", app.rating.average)
|
||||
txtReviewCount.text = app.rating.abbreviatedLabel
|
||||
}
|
||||
}
|
||||
|
||||
layoutUserReview.visibility =
|
||||
if (authProvider.isAnonymous) View.GONE else View.VISIBLE
|
||||
private fun updateUserReview(review: Review) {
|
||||
binding.layoutDetailsReview.apply {
|
||||
layoutUserReview.visibility = View.VISIBLE
|
||||
inputTitle.setText(review.title)
|
||||
inputReview.setText(review.comment)
|
||||
userStars.rating = review.rating.toFloat()
|
||||
|
||||
btnPostReview.setOnClickListener {
|
||||
if (authProvider.isAnonymous) {
|
||||
toast(R.string.toast_anonymous_restriction)
|
||||
} else {
|
||||
addOrUpdateReview(app, Review().apply {
|
||||
title = inputTitle.text.toString()
|
||||
rating = userStars.rating.toInt()
|
||||
comment = inputReview.text.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
headerRatingReviews.addClickListener {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToDetailsReviewFragment(
|
||||
app.displayName,
|
||||
app.packageName
|
||||
if (!authProvider.isAnonymous && app.isInstalled) {
|
||||
btnPostReview.setOnClickListener {
|
||||
addOrUpdateReview(
|
||||
app,
|
||||
Review().apply {
|
||||
title = inputTitle.text.toString()
|
||||
rating = userStars.rating.toInt()
|
||||
comment = inputReview.text.toString()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
layoutUserReview.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateAppDataSafety(app: App) {
|
||||
viewModel.fetchAppDataSafetyReport(app.packageName)
|
||||
}
|
||||
|
||||
private fun inflateAppPrivacy(app: App) {
|
||||
viewModel.fetchAppReport(app.packageName)
|
||||
}
|
||||
|
||||
private fun inflateAppDevInfo(app: App) {
|
||||
private fun updateAppDevInfo(app: App) {
|
||||
binding.layoutDetailsDev.apply {
|
||||
if (app.developerAddress.isNotEmpty()) {
|
||||
devAddress.apply {
|
||||
@@ -868,7 +840,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateBetaSubscription(app: App) {
|
||||
private fun updateBetaSubscription(app: App) {
|
||||
binding.layoutDetailsBeta.apply {
|
||||
app.testingProgram?.let { betaProgram ->
|
||||
if (betaProgram.isAvailable) {
|
||||
@@ -899,7 +871,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateAppStream(app: App) {
|
||||
private fun updateAppStream(app: App) {
|
||||
app.detailsStreamUrl?.let {
|
||||
val carouselController =
|
||||
DetailsCarouselController(object : GenericCarouselController.Callbacks {
|
||||
@@ -946,7 +918,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateAppPermission(app: App) {
|
||||
private fun updateAppPermission(app: App) {
|
||||
binding.layoutDetailsPermissions.apply {
|
||||
headerPermission.addClickListener {
|
||||
if (app.permissions.isNotEmpty()) {
|
||||
@@ -999,6 +971,13 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun warnAppUnavailable(app: App) {
|
||||
AuroraApp.events.send(InstallerEvent.Failed(app.packageName).apply {
|
||||
error = getString(R.string.status_unavailable)
|
||||
extra = getString(R.string.toast_app_unavailable)
|
||||
})
|
||||
}
|
||||
|
||||
/* App Review Helpers */
|
||||
|
||||
private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout {
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.Constants
|
||||
@@ -49,9 +48,8 @@ class DetailsExodusFragment : BaseFragment<FragmentGenericWithToolbarBinding>()
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.toolbar.apply {
|
||||
binding.toolbar.apply {
|
||||
title = args.displayName
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,7 @@ class DetailsMoreFragment : BaseFragment<FragmentDetailsMoreBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarActionMore.toolbar.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
binding.toolbar.setOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
inflateDescription(args.app)
|
||||
inflateFiles(args.app)
|
||||
@@ -95,7 +93,7 @@ class DetailsMoreFragment : BaseFragment<FragmentDetailsMoreBinding>() {
|
||||
}
|
||||
|
||||
private fun inflateDescription(app: App) {
|
||||
binding.layoutToolbarActionMore.txtTitle.text = app.displayName
|
||||
binding.toolbar.title = app.displayName
|
||||
binding.txtDescription.text = HtmlCompat.fromHtml(
|
||||
app.description,
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
|
||||
@@ -58,11 +58,9 @@ class DetailsReviewFragment : BaseFragment<FragmentDetailsReviewBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarActionReview.apply {
|
||||
txtTitle.text = args.displayName
|
||||
toolbar.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
binding.toolbar.apply {
|
||||
title = args.displayName
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
@@ -91,6 +89,7 @@ class DetailsReviewFragment : BaseFragment<FragmentDetailsReviewBinding>() {
|
||||
binding.chipGroup.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
when (checkedIds[0]) {
|
||||
R.id.filter_review_all -> filter = Review.Filter.ALL
|
||||
R.id.filter_newest_first -> filter = Review.Filter.NEWEST
|
||||
R.id.filter_review_critical -> filter = Review.Filter.CRITICAL
|
||||
R.id.filter_review_positive -> filter = Review.Filter.POSITIVE
|
||||
R.id.filter_review_five -> filter = Review.Filter.FIVE
|
||||
|
||||
@@ -21,12 +21,10 @@ package com.aurora.store.view.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.aurora.gplayapi.data.models.SearchBundle
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
@@ -49,9 +47,8 @@ class DevAppsFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.toolbar.apply {
|
||||
binding.toolbar.apply {
|
||||
title = args.developerName
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
|
||||
@@ -50,10 +50,9 @@ class DevProfileFragment : BaseFragment<FragmentDevProfileBinding>(),
|
||||
val developerCarouselController = DeveloperCarouselController(this)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.apply {
|
||||
txtTitle.text =
|
||||
if (args.title.isNullOrBlank()) getString(R.string.details_dev_profile) else args.title
|
||||
toolbar.setOnClickListener { findNavController().navigateUp() }
|
||||
binding.toolbar.apply {
|
||||
title = if (args.title.isNullOrBlank()) getString(R.string.details_dev_profile) else args.title
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
||||
// RecyclerView
|
||||
@@ -78,7 +77,7 @@ class DevProfileFragment : BaseFragment<FragmentDevProfileBinding>(),
|
||||
|
||||
is ViewState.Success<*> -> {
|
||||
(it.data as DevStream).apply {
|
||||
binding.layoutToolbarAction.txtTitle.text = title
|
||||
binding.toolbar.title = title
|
||||
binding.txtDevName.text = title
|
||||
binding.txtDevDescription.text = description
|
||||
binding.imgIcon.load(imgUrl)
|
||||
|
||||
@@ -22,7 +22,6 @@ package com.aurora.store.view.ui.downloads
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants.GITLAB_URL
|
||||
@@ -54,11 +53,7 @@ class DownloadFragment : BaseFragment<FragmentDownloadBinding>() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.toolbar.apply {
|
||||
elevation = 0f
|
||||
title = getString(R.string.title_download_manager)
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
inflateMenu(R.menu.menu_download_main)
|
||||
binding.toolbar.apply {
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
|
||||
@@ -50,7 +50,6 @@ import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.search.SearchResultViewModel
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -59,8 +58,6 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
|
||||
private val viewModel: SearchResultViewModel by viewModels()
|
||||
|
||||
private lateinit var searchView: TextInputEditText
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private var query: String? = null
|
||||
@@ -84,20 +81,20 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutViewToolbar.apply {
|
||||
searchView = inputSearch
|
||||
imgActionPrimary.setOnClickListener {
|
||||
binding.toolbar.apply {
|
||||
setNavigationOnClickListener {
|
||||
binding.searchBar.hideKeyboard()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
imgActionSecondary.setOnClickListener {
|
||||
findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
clearButton.apply {
|
||||
visibility = if (query.isNullOrBlank()) View.GONE else View.VISIBLE
|
||||
setOnClickListener {
|
||||
searchView.text?.clear()
|
||||
searchView.showKeyboard()
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_clear -> {
|
||||
binding.searchBar.text?.clear()
|
||||
binding.searchBar.showKeyboard()
|
||||
}
|
||||
R.id.action_download -> findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,30 +183,29 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.recycler
|
||||
.withModels {
|
||||
setFilterDuplicates(true)
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
|
||||
filteredAppList.forEach { app ->
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click(View.OnClickListener {
|
||||
searchView.hideKeyboard()
|
||||
openDetailsFragment(app.packageName, app)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (searchBundle.subBundles.isNotEmpty()) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
filteredAppList.forEach { app ->
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click(View.OnClickListener {
|
||||
binding.searchBar.hideKeyboard()
|
||||
openDetailsFragment(app.packageName, app)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (searchBundle.subBundles.isNotEmpty()) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.recycler.adapter?.let {
|
||||
if (it.itemCount < 10) {
|
||||
viewModel.next(searchBundle.subBundles)
|
||||
@@ -219,28 +215,22 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
}
|
||||
|
||||
private fun attachSearch() {
|
||||
searchView.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
if (s.isNotEmpty()) {
|
||||
binding.layoutViewToolbar.clearButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.layoutViewToolbar.clearButton.visibility = View.GONE
|
||||
}
|
||||
binding.toolbar.menu.findItem(R.id.action_clear)?.isVisible = s.isNotBlank()
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {}
|
||||
})
|
||||
|
||||
searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
binding.searchBar.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH
|
||||
|| actionId == KeyEvent.ACTION_DOWN
|
||||
|| actionId == KeyEvent.KEYCODE_ENTER
|
||||
) {
|
||||
query = searchView.text.toString()
|
||||
query = binding.searchBar.text.toString()
|
||||
query?.let {
|
||||
requireArguments().putString("query", it)
|
||||
queryViewModel(it)
|
||||
@@ -252,8 +242,8 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
}
|
||||
|
||||
private fun updateQuery(query: String) {
|
||||
searchView.text = Editable.Factory.getInstance().newEditable(query)
|
||||
searchView.setSelection(query.length)
|
||||
binding.searchBar.text = Editable.Factory.getInstance().newEditable(query)
|
||||
binding.searchBar.setSelection(query.length)
|
||||
queryViewModel(query)
|
||||
}
|
||||
|
||||
@@ -277,7 +267,6 @@ class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
.toList()
|
||||
}
|
||||
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
if (key == PREFERENCE_FILTER) query?.let { queryViewModel(it) }
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.aurora.store.databinding.FragmentSearchSuggestionBinding
|
||||
import com.aurora.store.view.epoxy.views.SearchSuggestionViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.search.SearchSuggestionViewModel
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -47,26 +46,21 @@ class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>()
|
||||
|
||||
private val viewModel: SearchSuggestionViewModel by viewModels()
|
||||
|
||||
private lateinit var searchView: TextInputEditText
|
||||
|
||||
private var query: String = String()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarSearch.apply {
|
||||
searchView = inputSearch
|
||||
imgActionPrimary.setOnClickListener {
|
||||
searchView.hideKeyboard()
|
||||
binding.toolbar.apply {
|
||||
setNavigationOnClickListener {
|
||||
binding.searchBar.hideKeyboard()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
imgActionSecondary.setOnClickListener {
|
||||
findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
clearButton.apply {
|
||||
visibility = if (query.isBlank()) View.GONE else View.VISIBLE
|
||||
setOnClickListener { searchView.text?.clear() }
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_clear -> binding.searchBar.text?.clear()
|
||||
R.id.action_download -> findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +75,7 @@ class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>()
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (::searchView.isInitialized) {
|
||||
searchView.showKeyboard()
|
||||
}
|
||||
binding.searchBar.showKeyboard()
|
||||
}
|
||||
|
||||
private fun updateController(searchSuggestions: List<SearchSuggestEntry>) {
|
||||
@@ -98,7 +90,7 @@ class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>()
|
||||
updateQuery(it.title)
|
||||
}
|
||||
.click { _ ->
|
||||
searchView.hideKeyboard()
|
||||
binding.searchBar.hideKeyboard()
|
||||
search(it.title)
|
||||
}
|
||||
)
|
||||
@@ -107,32 +99,30 @@ class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>()
|
||||
}
|
||||
|
||||
private fun setupSearch() {
|
||||
searchView.addTextChangedListener(object : TextWatcher {
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
if (s.isNotEmpty()) {
|
||||
query = s.toString()
|
||||
val query = s.toString()
|
||||
if (query.isNotEmpty()) {
|
||||
viewModel.observeStreamBundles(query)
|
||||
}
|
||||
binding.layoutToolbarSearch.clearButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.layoutToolbarSearch.clearButton.visibility = View.GONE
|
||||
}
|
||||
binding.toolbar.menu.findItem(R.id.action_clear)?.isVisible = s.isNotBlank()
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {}
|
||||
})
|
||||
|
||||
searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
binding.searchBar.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH
|
||||
|| actionId == KeyEvent.ACTION_DOWN
|
||||
|| actionId == KeyEvent.KEYCODE_ENTER
|
||||
) {
|
||||
query = searchView.text.toString()
|
||||
val query = binding.searchBar.text.toString()
|
||||
if (query.isNotEmpty()) {
|
||||
searchView.hideKeyboard()
|
||||
binding.searchBar.hideKeyboard()
|
||||
search(query)
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
@@ -142,8 +132,8 @@ class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>()
|
||||
}
|
||||
|
||||
private fun updateQuery(query: String) {
|
||||
searchView.text = Editable.Factory.getInstance().newEditable(query)
|
||||
searchView.setSelection(query.length)
|
||||
binding.searchBar.text = Editable.Factory.getInstance().newEditable(query)
|
||||
binding.searchBar.setSelection(query.length)
|
||||
}
|
||||
|
||||
private fun search(query: String) {
|
||||
|
||||
@@ -19,19 +19,35 @@
|
||||
|
||||
package com.aurora.store.view.ui.splash
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Intent
|
||||
import android.net.UrlQuerySanitizer
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.isMAndAbove
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.AuthState
|
||||
import com.aurora.store.databinding.FragmentSplashBinding
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
|
||||
@@ -44,8 +60,20 @@ import kotlinx.coroutines.launch
|
||||
@AndroidEntryPoint
|
||||
class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
|
||||
private val TAG = SplashFragment::class.java.simpleName
|
||||
|
||||
private val viewModel: AuthViewModel by activityViewModels()
|
||||
|
||||
private val startForAccount =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val accountName = it.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
|
||||
if (!accountName.isNullOrBlank()) {
|
||||
requestAuthTokenForGoogle(accountName)
|
||||
} else {
|
||||
runOnUiThread { resetActions() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -57,9 +85,7 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.toolbar.apply {
|
||||
elevation = 0f
|
||||
inflateMenu(R.menu.menu_splash)
|
||||
binding.toolbar.apply {
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_blacklist_manager -> {
|
||||
@@ -158,10 +184,10 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
private fun updateActionLayout(isVisible: Boolean) {
|
||||
if (isVisible) {
|
||||
binding.layoutAction.show()
|
||||
binding.layoutToolbarAction.toolbar.visibility = View.VISIBLE
|
||||
binding.toolbar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.layoutAction.hide()
|
||||
binding.layoutToolbarAction.toolbar.visibility = View.GONE
|
||||
binding.toolbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +202,29 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
binding.btnGoogle.addOnClickListener {
|
||||
if (viewModel.authState.value != AuthState.Fetching) {
|
||||
binding.btnGoogle.updateProgress(true)
|
||||
findNavController().navigate(R.id.googleFragment)
|
||||
if (isMAndAbove && PackageUtil.hasSupportedMicroG(requireContext())) {
|
||||
val accounts = fetchGoogleAccounts()
|
||||
|
||||
// Do not show selection dialog if there is only one account available
|
||||
if (accounts.isNotEmpty() && accounts.size == 1) {
|
||||
requestAuthTokenForGoogle(accounts.first().name)
|
||||
return@addOnClickListener
|
||||
}
|
||||
|
||||
Log.i(TAG, "Found supported microG, trying to request credentials")
|
||||
val accountIntent = AccountManager.newChooseAccountIntent(
|
||||
null,
|
||||
null,
|
||||
arrayOf(GOOGLE_ACCOUNT_TYPE),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
startForAccount.launch(accountIntent)
|
||||
} else {
|
||||
findNavController().navigate(R.id.googleFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +242,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
private fun navigateToDefaultTab() {
|
||||
val defaultDestination = Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
|
||||
val defaultDestination =
|
||||
Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
|
||||
val directions =
|
||||
when (requireArguments().getInt("destinationId", defaultDestination)) {
|
||||
R.id.updatesFragment -> {
|
||||
@@ -206,19 +255,49 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
2 -> SplashFragmentDirections.actionSplashFragmentToUpdatesFragment()
|
||||
else -> SplashFragmentDirections.actionSplashFragmentToNavigationApps()
|
||||
}
|
||||
activity?.viewModelStore?.clear() // Clear ViewModelStore to avoid bugs with logout
|
||||
requireActivity().viewModelStore.clear() // Clear ViewModelStore to avoid bugs with logout
|
||||
findNavController().navigate(directions)
|
||||
}
|
||||
|
||||
private fun getPackageName(): String {
|
||||
// Navigation component cannot handle market scheme as its missing a valid host
|
||||
return if (activity?.intent != null && activity?.intent?.scheme == "market") {
|
||||
return if (requireActivity().intent != null && requireActivity().intent.scheme == "market") {
|
||||
requireActivity().intent.data!!.getQueryParameter("id") ?: ""
|
||||
} else if (activity?.intent != null && activity?.intent?.action == Intent.ACTION_SEND) {
|
||||
val clipData = activity?.intent?.getStringExtra(Intent.EXTRA_TEXT) ?: ""
|
||||
} else if (requireActivity().intent != null && requireActivity().intent.action == Intent.ACTION_SEND) {
|
||||
val clipData = requireActivity().intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
|
||||
UrlQuerySanitizer(clipData).getValue("id") ?: ""
|
||||
} else {
|
||||
requireArguments().getString("packageName") ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchGoogleAccounts(): Array<Account> {
|
||||
val accountManager = AccountManager.get(requireContext())
|
||||
return accountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE)
|
||||
}
|
||||
|
||||
private fun requestAuthTokenForGoogle(accountName: String) {
|
||||
try {
|
||||
AccountManager.get(requireContext())
|
||||
.getAuthToken(
|
||||
Account(accountName, GOOGLE_ACCOUNT_TYPE),
|
||||
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
|
||||
bundleOf(
|
||||
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
|
||||
"overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
|
||||
),
|
||||
requireActivity(),
|
||||
{
|
||||
viewModel.buildGoogleAuthData(
|
||||
accountName,
|
||||
it.result.getString(AccountManager.KEY_AUTHTOKEN) ?: "",
|
||||
AuthHelper.Token.AUTH
|
||||
)
|
||||
},
|
||||
Handler(Looper.getMainLooper())
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to get authToken for Google login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -34,7 +33,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.providers.NativeDeviceInfoProvider
|
||||
import com.aurora.store.databinding.FragmentGenericWithPagerBinding
|
||||
import com.aurora.store.databinding.FragmentSpoofBinding
|
||||
import com.aurora.store.util.PathUtil
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -42,7 +41,7 @@ import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SpoofFragment : BaseFragment<FragmentGenericWithPagerBinding>() {
|
||||
class SpoofFragment : BaseFragment<FragmentSpoofBinding>() {
|
||||
private val TAG = SpoofFragment::class.java.simpleName
|
||||
|
||||
// Android is weird, even if export device config with proper mime type, it will refuse to open
|
||||
@@ -63,11 +62,7 @@ class SpoofFragment : BaseFragment<FragmentGenericWithPagerBinding>() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutActionToolbar.toolbar.apply {
|
||||
elevation = 0f
|
||||
title = getString(R.string.title_spoof_manager)
|
||||
navigationIcon = ContextCompat.getDrawable(view.context, R.drawable.ic_arrow_back)
|
||||
inflateMenu(R.menu.menu_import_export)
|
||||
binding.toolbar.apply {
|
||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
|
||||
@@ -21,11 +21,14 @@ package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -37,6 +40,7 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class BlacklistViewModel @Inject constructor(
|
||||
val blacklistProvider: BlacklistProvider,
|
||||
val gson: Gson,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -60,4 +64,44 @@ class BlacklistViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAll() {
|
||||
selected.addAll(packages.value?.map { it.packageName } ?: emptyList())
|
||||
}
|
||||
|
||||
fun removeAll() {
|
||||
selected.clear()
|
||||
}
|
||||
|
||||
fun importBlacklist(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use {
|
||||
val importedSet: MutableSet<String> = gson.fromJson(
|
||||
it.bufferedReader().readText(),
|
||||
object : TypeToken<MutableSet<String?>?>() {}.type
|
||||
)
|
||||
|
||||
val knownSet = blacklistProvider.blacklist
|
||||
knownSet.addAll(importedSet)
|
||||
|
||||
selected = knownSet
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to import blacklist", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportBlacklist(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(blacklistProvider.blacklist).encodeToByteArray())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to export blacklist", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
@@ -39,26 +40,38 @@ class FavouriteViewModel @Inject constructor(
|
||||
private val favouriteDao: FavouriteDao,
|
||||
private val gson: Gson
|
||||
) : ViewModel() {
|
||||
private val TAG = FavouriteViewModel::class.java.simpleName
|
||||
|
||||
val favouritesList = favouriteDao.favourites()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun importFavourites(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
context.contentResolver.openInputStream(uri)?.use {
|
||||
val importExport =
|
||||
gson.fromJson(it.bufferedReader().readText(), ImportExport::class.java)
|
||||
favouriteDao.insertAll(
|
||||
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) }
|
||||
)
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use {
|
||||
val importExport = gson.fromJson(
|
||||
it.bufferedReader().readText(),
|
||||
ImportExport::class.java
|
||||
)
|
||||
|
||||
favouriteDao.insertAll(
|
||||
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) }
|
||||
)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to import favourites", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportFavourites(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favouritesList.value!!)).encodeToByteArray())
|
||||
try {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favouritesList.value!!)).encodeToByteArray())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to export favourites", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,17 @@
|
||||
package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.ImportExport
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -35,13 +41,16 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class InstalledViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
@ApplicationContext private val context: Context,
|
||||
private val blacklistProvider: BlacklistProvider,
|
||||
private val gson: Gson,
|
||||
private val webAppDetailsHelper: WebAppDetailsHelper
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = InstalledViewModel::class.java.simpleName
|
||||
|
||||
private val _packages = MutableStateFlow<List<PackageInfo>?>(null)
|
||||
val packages = _packages.asStateFlow()
|
||||
private val _apps = MutableStateFlow<List<App>?>(null)
|
||||
val apps = _apps.asStateFlow()
|
||||
|
||||
init {
|
||||
fetchApps()
|
||||
@@ -50,10 +59,35 @@ class InstalledViewModel @Inject constructor(
|
||||
fun fetchApps() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_packages.value = PackageUtil.getAllValidPackages(context)
|
||||
val packages = PackageUtil.getAllValidPackages(context)
|
||||
.filterNot { blacklistProvider.isBlacklisted(it.packageName) }
|
||||
|
||||
// Divide the list of packages into chunks of 100 & fetch app details
|
||||
// 50 is a safe number to avoid hitting the rate limit or package size limit
|
||||
val chunkedPackages = packages.chunked(50)
|
||||
val allApps = chunkedPackages.flatMap { chunk ->
|
||||
webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
|
||||
}
|
||||
|
||||
_apps.emit(allApps)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch apps", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportApps(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val favourites: List<Favourite> = apps.value!!.map { app ->
|
||||
Favourite.fromApp(app, Favourite.Mode.IMPORT)
|
||||
}
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favourites)).encodeToByteArray())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to installed apps", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
||||
@@ -11,19 +13,26 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper
|
||||
import com.aurora.gplayapi.network.IHttpClient
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.helper.DownloadHelper
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.FavouriteDao
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.google.gson.GsonBuilder
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import java.lang.reflect.Modifier
|
||||
@@ -32,6 +41,7 @@ import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport
|
||||
|
||||
@HiltViewModel
|
||||
class AppDetailsViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val appDetailsHelper: AppDetailsHelper,
|
||||
private val reviewsHelper: ReviewsHelper,
|
||||
private val webDataSafetyHelper: WebDataSafetyHelper,
|
||||
@@ -69,7 +79,10 @@ class AppDetailsViewModel @Inject constructor(
|
||||
private val _favourite = MutableStateFlow<Boolean>(false)
|
||||
val favourite = _favourite.asStateFlow()
|
||||
|
||||
val downloadsList get() = downloadHelper.downloadsList
|
||||
val download = combine(app, downloadHelper.downloadsList) { a, list ->
|
||||
if (a.packageName.isBlank()) return@combine null
|
||||
list.find { d -> d.packageName == a.packageName }
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
|
||||
fun fetchAppDetails(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
@@ -77,7 +90,9 @@ class AppDetailsViewModel @Inject constructor(
|
||||
checkFavourite(packageName)
|
||||
|
||||
val app: App = appStash.getOrPut(packageName) {
|
||||
appDetailsHelper.getAppByPackageName(packageName)
|
||||
appDetailsHelper.getAppByPackageName(packageName).apply {
|
||||
isInstalled = PackageUtil.isInstalled(context, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
_app.emit(app)
|
||||
@@ -148,20 +163,42 @@ class AppDetailsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchUserAppReview(app: App) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val stashedUserReview = userReviewStash[app.packageName]
|
||||
if (stashedUserReview != null) {
|
||||
_userReview.emit(stashedUserReview)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val isTesting = app.testingProgram?.isSubscribed ?: false
|
||||
val userReview = reviewsHelper.getUserReview(app.packageName, isTesting)
|
||||
|
||||
if (userReview != null) {
|
||||
userReviewStash[app.packageName] = userReview
|
||||
_userReview.emit(userReview)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch user review", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun postAppReview(packageName: String, review: Review, isBeta: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val userReview = userReviewStash.getOrPut(packageName) {
|
||||
reviewsHelper.addOrEditReview(
|
||||
packageName,
|
||||
review.title,
|
||||
review.comment,
|
||||
review.rating,
|
||||
isBeta
|
||||
)
|
||||
}
|
||||
val userReview = reviewsHelper.addOrEditReview(
|
||||
packageName,
|
||||
review.title,
|
||||
review.comment,
|
||||
review.rating,
|
||||
isBeta
|
||||
)
|
||||
|
||||
if (userReview != null) {
|
||||
context.toast(R.string.toast_rated_success)
|
||||
userReviewStash[packageName] = userReview
|
||||
_userReview.emit(userReview)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
@@ -206,8 +243,8 @@ class AppDetailsViewModel @Inject constructor(
|
||||
|
||||
private fun getLatestExodusReport(packageName: String): Report? {
|
||||
val headers: MutableMap<String, String> = mutableMapOf()
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Content-Type"] = Constants.JSON_MIME_TYPE
|
||||
headers["Accept"] = Constants.JSON_MIME_TYPE
|
||||
headers["Authorization"] = "Token ${BuildConfig.EXODUS_API_KEY}"
|
||||
|
||||
val url = Constants.EXODUS_SEARCH_URL + packageName
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners
|
||||
android:topLeftRadius="14dp"
|
||||
android:topRightRadius="14dp" />
|
||||
<solid android:color="?colorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="@color/colorRedAlt" />
|
||||
</shape>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="15dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?colorControlHighlight" />
|
||||
</shape>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Disabled -->
|
||||
<item android:color="@android:color/transparent" android:state_enabled="false"/>
|
||||
|
||||
<!-- Selected -->
|
||||
<item android:color="?attr/colorControlActivated" android:state_selected="true"/>
|
||||
<item android:color="?attr/colorControlHighlight" android:state_checked="true"/>
|
||||
|
||||
<!-- Not selected -->
|
||||
<item android:color="?android:colorBackground" />
|
||||
</selector>
|
||||
11
app/src/main/res/drawable/ic_apk_install.xml
Normal file
11
app/src/main/res/drawable/ic_apk_install.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,160Q80,127 103.5,103.5Q127,80 160,80L480,80L720,320L720,490L640,490L640,360L440,360L440,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800L600,800L600,880L160,880ZM160,800L160,490L160,490L160,360L160,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800ZM200,760Q204,711 230,670Q256,629 298,605L260,537Q260,536 264,522Q269,520 273.5,520Q278,520 280,525L319,595Q339,587 359,582.5Q379,578 400,578Q421,578 441,582.5Q461,587 481,595L520,525Q520,525 535,521Q540,523 541,528Q542,533 540,537L502,605Q544,629 570,670Q596,711 600,760L200,760ZM310,700Q318,700 324,694Q330,688 330,680Q330,672 324,666Q318,660 310,660Q302,660 296,666Q290,672 290,680Q290,688 296,694Q302,700 310,700ZM490,700Q498,700 504,694Q510,688 510,680Q510,672 504,666Q498,660 490,660Q482,660 476,666Q470,672 470,680Q470,688 476,694Q482,700 490,700ZM800,880L640,720L696,663L760,726L760,560L840,560L840,726L904,663L960,720L800,880Z"/>
|
||||
</vector>
|
||||
@@ -27,9 +27,14 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.about.AboutFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_about" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -27,9 +27,14 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.account.AccountFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_account_manager" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/chip_layout"
|
||||
@@ -140,4 +145,4 @@
|
||||
app:btnStateText="@string/action_logout" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -28,9 +28,13 @@
|
||||
android:weightSum="2"
|
||||
tools:context=".view.ui.splash.SplashFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_splash" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -26,9 +26,14 @@
|
||||
android:weightSum="3"
|
||||
tools:context=".view.ui.about.AboutFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_about" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -28,9 +28,14 @@
|
||||
android:weightSum="2"
|
||||
tools:context=".view.ui.account.AccountFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_account_manager" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -131,4 +136,4 @@
|
||||
app:btnStateIcon="@drawable/ic_logout"
|
||||
app:btnStateText="@string/action_logout" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -31,9 +31,14 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_toolbar"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_details"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scrollView"
|
||||
@@ -119,8 +124,4 @@
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_install"
|
||||
layout="@layout/layout_details_install" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -25,9 +25,13 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.details.DetailsMoreFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action_more"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
@@ -100,4 +104,4 @@
|
||||
tools:listitem="@layout/view_file" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -25,9 +25,13 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.details.DetailsReviewFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action_review"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/sort_view"
|
||||
@@ -53,6 +57,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/filter_review_all" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_newest_first"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/filter_latest" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_review_critical"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
@@ -117,4 +128,4 @@
|
||||
app:itemSpacing="@dimen/margin_normal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_review" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -27,10 +27,13 @@
|
||||
android:showDividers="middle"
|
||||
tools:context=".view.ui.details.DevProfileFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_action" />
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
@@ -98,4 +101,4 @@
|
||||
android:layout_centerInParent="true" />
|
||||
</RelativeLayout>
|
||||
</ViewFlipper>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -25,9 +25,15 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".view.ui.downloads.DownloadFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_download_main"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_download_manager" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler"
|
||||
@@ -38,4 +44,4 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:stackFromEnd="false"
|
||||
tools:listitem="@layout/view_download" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -24,18 +24,35 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_native"
|
||||
layout="@layout/view_toolbar_search" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/searchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@null"
|
||||
android:hint="@string/search_hint"
|
||||
android:imeOptions="flagNoExtractUi|actionSearch"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/layout_toolbar_native"
|
||||
android:layout_below="@+id/toolbar"
|
||||
android:clipToPadding="true"
|
||||
android:paddingBottom="@dimen/height_bottom_adj"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:stackFromEnd="false"
|
||||
tools:listitem="@layout/view_app_list" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -23,17 +23,21 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_native"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/layout_toolbar_native"
|
||||
android:layout_below="@+id/toolbar"
|
||||
android:clipToPadding="true"
|
||||
android:paddingBottom="@dimen/height_bottom_adj"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:stackFromEnd="false" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -30,15 +30,33 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_view_toolbar"
|
||||
layout="@layout/view_toolbar_search" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_search"
|
||||
app:navigationIcon="@drawable/ic_arrow_back">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/searchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@null"
|
||||
android:hint="@string/search_hint"
|
||||
android:imeOptions="flagNoExtractUi|actionSearch"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/layout_view_toolbar" />
|
||||
android:layout_below="@id/toolbar" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -29,15 +30,33 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_search"
|
||||
layout="@layout/view_toolbar_search" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_search"
|
||||
app:navigationIcon="@drawable/ic_arrow_back">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/searchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@null"
|
||||
android:hint="@string/search_hint"
|
||||
android:imeOptions="flagNoExtractUi|actionSearch"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/layout_toolbar_search" />
|
||||
android:layout_below="@id/toolbar" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recycler"
|
||||
@@ -46,4 +65,4 @@
|
||||
android:layout_below="@+id/divider"
|
||||
tools:listitem="@layout/view_search_suggestion" />
|
||||
</RelativeLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -28,9 +28,13 @@
|
||||
android:weightSum="2"
|
||||
tools:context=".view.ui.splash.SplashFragment">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_splash" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/layout_top"
|
||||
|
||||
@@ -22,15 +22,21 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_action_toolbar"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:menu="@menu/menu_import_export"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:title="@string/title_spoof_manager" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/layout_action_toolbar"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="@android:color/transparent"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicator="@drawable/tab_indicator"
|
||||
@@ -44,4 +50,4 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/tab_layout" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
@@ -33,8 +33,7 @@
|
||||
android:id="@+id/top_tab_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:padding="@dimen/padding_medium"
|
||||
app:chipSpacingHorizontal="@dimen/margin_small"
|
||||
app:selectionRequired="true"
|
||||
app:singleLine="true"
|
||||
@@ -42,14 +41,14 @@
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/tab_top_free"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
style="@style/Chip.TopChart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/tab_top_free" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/tab_top_grossing"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
style="@style/Chip.TopChart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/tab_top_grossing" />
|
||||
@@ -57,14 +56,14 @@
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/tab_trending"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
style="@style/Chip.TopChart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/tab_trending" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/tab_top_paid"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
style="@style/Chip.TopChart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/tab_top_paid" />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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"
|
||||
@@ -26,18 +27,35 @@
|
||||
android:paddingEnd="@dimen/padding_small"
|
||||
android:paddingBottom="@dimen/padding_small">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
tools:src="@drawable/bg_placeholder" />
|
||||
<RelativeLayout
|
||||
android:id="@+id/img_icon_layout"
|
||||
android:layout_width="@dimen/icon_size_large"
|
||||
android:layout_height="@dimen/icon_size_large">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="@dimen/icon_size_large"
|
||||
android:layout_height="@dimen/icon_size_large"
|
||||
tools:src="@drawable/bg_placeholder" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progress_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorSize="@dimen/icon_size_large"
|
||||
app:trackThickness="3dp"
|
||||
tools:progress="40" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_toEndOf="@id/img_icon"
|
||||
android:layout_toEndOf="@id/img_icon_layout"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle"
|
||||
tools:text="App Name" />
|
||||
@@ -84,4 +102,31 @@
|
||||
android:layout_alignEnd="@id/txt_line1"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2"
|
||||
tools:text="Free" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_line4"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
android:weightSum="2">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_secondary_action"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
tools:text="@string/title_manual_download" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_primary_action"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
tools:text="@string/action_install" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_small"
|
||||
android:showDividers="middle"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
@@ -40,7 +42,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
@@ -61,11 +62,9 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_beta_Action"
|
||||
android:layout_width="wrap_content"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:insetTop="@dimen/margin_xsmall"
|
||||
android:insetBottom="@dimen/margin_xsmall"
|
||||
android:minWidth="128dp"
|
||||
android:text="@string/action_join"
|
||||
app:cornerRadius="@dimen/radius_small" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -34,70 +34,52 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/layout_extras"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/layout_extras"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider_line"
|
||||
android:dividerPadding="@dimen/padding_xsmall"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/padding_small"
|
||||
android:showDividers="middle">
|
||||
android:paddingStart="@dimen/padding_medium"
|
||||
android:paddingEnd="@dimen/padding_medium"
|
||||
app:selectionRequired="false"
|
||||
app:singleLine="true">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/txt_rating"
|
||||
style="@style/Chip.Tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_star"
|
||||
app:drawableTint="?colorAccent"
|
||||
app:chipIcon="@drawable/ic_star"
|
||||
tools:text="3.5" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/txt_installs"
|
||||
style="@style/Chip.Tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_download_manager"
|
||||
app:drawableTint="?colorAccent"
|
||||
app:chipIcon="@drawable/ic_download_manager"
|
||||
tools:text="2500" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/txt_size"
|
||||
style="@style/Chip.Tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
app:chipIcon="@drawable/ic_apk_install"
|
||||
tools:text="25 MB" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/txt_updated"
|
||||
style="@style/Chip.Tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
app:chipIcon="@drawable/ic_updates"
|
||||
tools:text="Jan 21 2020" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/bottomSheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_bottomsheet"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_small"
|
||||
android:showDividers="middle"
|
||||
app:behavior_hideable="false"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
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.layouts.button.ActionButton
|
||||
android:id="@+id/btn_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_marginTop="@dimen/margin_xsmall"
|
||||
android:layout_marginBottom="@dimen/margin_xsmall"
|
||||
android:text="@string/action_install"
|
||||
app:btnActionText="@string/action_install"
|
||||
app:btnActionTextColor="?colorOnPrimary" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/progress_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_marginTop="@dimen/margin_xsmall"
|
||||
android:layout_marginBottom="@dimen/margin_xsmall"
|
||||
android:gravity="center_vertical"
|
||||
android:weightSum="4">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_download"
|
||||
style="@style/Widget.Material3.LinearProgressIndicator.Legacy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/img_cancel"
|
||||
android:background="@drawable/bg_rounded_transparent"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="?colorOnPrimary"
|
||||
app:indicatorColor="?colorControlHighlight"
|
||||
app:trackColor="?colorPrimary"
|
||||
app:trackThickness="@dimen/icon_size_small" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toStartOf="@id/img_cancel"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="3">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_progress_percent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xsmall"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="0%"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Title"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textSize="32sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:layout_weight="2"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_speed"
|
||||
style="@style/TextAppearance.Aurora.Line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/download_speed_estimating"
|
||||
android:textColor="?colorOnPrimary" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_eta"
|
||||
style="@style/TextAppearance.Aurora.Line2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/download_eta_calculating"
|
||||
android:textColor="?colorOnPrimary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_cancel"
|
||||
android:layout_width="@dimen/icon_size_small"
|
||||
android:layout_height="@dimen/icon_size_small"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:background="@drawable/bg_cancel"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:tint="@color/colorWhite"
|
||||
app:srcCompat="@drawable/ic_cancel" />
|
||||
</RelativeLayout>
|
||||
</ViewFlipper>
|
||||
</LinearLayout>
|
||||
@@ -19,7 +19,7 @@
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/exodus_card"
|
||||
android:layout_width="match_parent"
|
||||
@@ -41,20 +41,14 @@
|
||||
android:id="@+id/txt_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/header_privacy"
|
||||
android:text="@string/exodus_progress"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:id="@+id/btn_request_analysis"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txt_status"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:insetTop="@dimen/padding_small"
|
||||
android:insetBottom="@dimen/padding_xsmall"
|
||||
android:text="@string/action_request_analysis"
|
||||
app:cornerRadius="@dimen/margin_small" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -38,8 +38,9 @@
|
||||
android:id="@+id/layout_user_review"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/user_stars"
|
||||
@@ -55,38 +56,39 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/margin_normal"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_title"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@drawable/bg_changelog"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:hint="@string/details_ratings_title_hint"
|
||||
android:imeOptions="flagNoExtractUi|actionDone"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_review"
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:hint="@string/details_ratings_title_hint"
|
||||
android:inputType="text"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:background="@drawable/bg_changelog"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:hint="@string/details_think_this_app"
|
||||
android:imeOptions="flagNoExtractUi|actionDone"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_review"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:hint="@string/details_think_this_app"
|
||||
android:inputType="textMultiLine"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line2" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_post_review"
|
||||
@@ -94,7 +96,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/action_post"
|
||||
app:cornerRadius="@dimen/margin_small" />
|
||||
app:cornerRadius="@dimen/radius_small" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ Aurora Store is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 2 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Aurora Store is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_toolbar_action"
|
||||
layout="@layout/view_toolbar_native" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
@@ -87,21 +87,21 @@
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_gfs"
|
||||
style="@style/AppTheme.FilterChip"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_gsf_dependent_apps" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_paid"
|
||||
style="@style/AppTheme.FilterChip"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_paid_apps" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_ads"
|
||||
style="@style/AppTheme.FilterChip"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_apps_with_ads" />
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_button">
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateFirstView="true"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn"
|
||||
style="@style/Widget.Material3.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"
|
||||
tools:text="Install" />
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:paddingStart="@dimen/padding_medium"
|
||||
android:paddingEnd="@dimen/padding_medium">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_action_primary"
|
||||
android:layout_width="@dimen/icon_size_default"
|
||||
android:layout_height="@dimen/icon_size_default"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_back"
|
||||
app:srcCompat="@drawable/ic_arrow_back"
|
||||
app:tint="?colorControlNormal" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_title"
|
||||
style="@style/AuroraTextStyle.Subtitle.Alt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/img_action_primary"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true"
|
||||
tools:text="Title" />
|
||||
</RelativeLayout>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
app:elevation="0dp">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowBackground"
|
||||
app:layout_scrollFlags="enterAlways|exitUntilCollapsed" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -1,77 +0,0 @@
|
||||
<?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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_large">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_action_primary"
|
||||
android:layout_width="@dimen/icon_size_default"
|
||||
android:layout_height="@dimen/icon_size_default"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_back"
|
||||
app:srcCompat="@drawable/ic_arrow_back"
|
||||
app:tint="?colorControlNormal" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/clearButton"
|
||||
android:layout_toEndOf="@id/img_action_primary"
|
||||
android:background="@null"
|
||||
android:hint="@string/search_hint"
|
||||
android:imeOptions="flagNoExtractUi|actionSearch"
|
||||
android:inputType="text"
|
||||
android:paddingStart="@dimen/padding_large"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:singleLine="true" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/clearButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/img_action_secondary"
|
||||
android:contentDescription="@string/details_changelog"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_cancel"
|
||||
app:iconTint="?attr/colorControlNormal" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_action_secondary"
|
||||
android:layout_width="@dimen/icon_size_default"
|
||||
android:layout_height="@dimen/icon_size_default"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/title_download_manager"
|
||||
app:srcCompat="@drawable/ic_arrow_download"
|
||||
app:tint="?colorControlNormal" />
|
||||
</RelativeLayout>
|
||||
@@ -17,11 +17,17 @@
|
||||
~
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<solid android:color="?android:colorBackground" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_select_all"
|
||||
android:title="@string/action_select_all" />
|
||||
<item
|
||||
android:id="@+id/action_remove_all"
|
||||
android:title="@string/action_remove_all" />
|
||||
<item
|
||||
android:id="@+id/action_import"
|
||||
android:title="@string/action_import" />
|
||||
<item
|
||||
android:id="@+id/action_export"
|
||||
android:title="@string/action_export" />
|
||||
</menu>
|
||||
15
app/src/main/res/menu/menu_search.xml
Normal file
15
app/src/main/res/menu/menu_search.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_clear"
|
||||
android:icon="@drawable/ic_cancel"
|
||||
android:title="@string/action_clear"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_download"
|
||||
android:icon="@drawable/ic_download_manager"
|
||||
android:title="@string/title_download_manager"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -71,12 +71,12 @@
|
||||
android:id="@+id/appsGamesFragment"
|
||||
android:name="com.aurora.store.view.ui.all.AppsGamesFragment"
|
||||
android:label="@string/title_apps_games"
|
||||
tools:layout="@layout/fragment_generic_with_pager" />
|
||||
tools:layout="@layout/fragment_generic_with_search" />
|
||||
<fragment
|
||||
android:id="@+id/spoofFragment"
|
||||
android:name="com.aurora.store.view.ui.spoof.SpoofFragment"
|
||||
android:label="@string/title_spoof_manager"
|
||||
tools:layout="@layout/fragment_generic_with_pager" />
|
||||
tools:layout="@layout/fragment_spoof" />
|
||||
<fragment
|
||||
android:id="@+id/favouriteFragment"
|
||||
android:name="com.aurora.store.view.ui.commons.FavouriteFragment"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.DynamicColors.Dark.NoActionBar">
|
||||
<item name="chipStyle">@style/AppTheme.FilterChip</item>
|
||||
<item name="chipStyle">@style/Chip.Filter</item>
|
||||
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
||||
<item name="android:statusBarColor">@color/colorTransparent</item>
|
||||
</style>
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
<attr name="btnStateIcon" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ActionButton">
|
||||
<attr name="btnActionText" format="string" />
|
||||
<attr name="btnActionTextColor" format="string" />
|
||||
<attr name="btnActionIcon" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AuroraProgressView">
|
||||
<attr name="minWidth" format="dimension" />
|
||||
<attr name="maxWidth" format="dimension" />
|
||||
@@ -48,4 +42,4 @@
|
||||
<attr name="txtSubtitle" format="string" />
|
||||
<attr name="imgIcon" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -85,10 +85,12 @@
|
||||
<string name="action_pause">"Pause"</string>
|
||||
<string name="action_pending">"Pending"</string>
|
||||
<string name="action_post">"Post"</string>
|
||||
<string name="action_remove_all">"Remove all"</string>
|
||||
<string name="action_request_analysis">Request new analysis</string>
|
||||
<string name="action_restart">"Restart"</string>
|
||||
<string name="action_resume">"Resume"</string>
|
||||
<string name="action_search">"Search"</string>
|
||||
<string name="action_select_all">"Select all"</string>
|
||||
<string name="action_share">"Share"</string>
|
||||
<string name="action_uninstall">"Uninstall"</string>
|
||||
<string name="action_uninstall_success">"Successfully uninstalled"</string>
|
||||
@@ -156,6 +158,7 @@
|
||||
<string name="exodus_substring">known trackers(s) found in</string>
|
||||
<string name="exodus_view_report">"View report"</string>
|
||||
<string name="filter_review_all">"All"</string>
|
||||
<string name="filter_latest">"Latest"</string>
|
||||
<string name="filter_review_critical">"Critical"</string>
|
||||
<string name="filter_review_five">"Five"</string>
|
||||
<string name="filter_review_four">"Four"</string>
|
||||
@@ -437,6 +440,12 @@
|
||||
<string name="toast_fav_import_success">Favourites imported!</string>
|
||||
<string name="toast_fav_export_success">Favourites exported!</string>
|
||||
|
||||
<!-- BlacklistFragment-->
|
||||
<string name="toast_black_import_failed">Failed to import blacklist!</string>
|
||||
<string name="toast_black_export_failed">Failed to export blacklist!</string>
|
||||
<string name="toast_black_import_success">Blacklist imported!</string>
|
||||
<string name="toast_black_export_success">Blacklist exported!</string>
|
||||
|
||||
<!-- ExportWorker -->
|
||||
<string name="export_app_title">File Exporter</string>
|
||||
<string name="export_app_summary">Hold on, exporting your file</string>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.DynamicColors.Light.NoActionBar">
|
||||
<item name="chipStyle">@style/AppTheme.FilterChip</item>
|
||||
<item name="chipStyle">@style/Chip.Filter</item>
|
||||
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
||||
<item name="bottomSheetStyle">@style/AppTheme.BottomSheetStyle</item>
|
||||
<item name="android:statusBarColor">@color/colorTransparent</item>
|
||||
@@ -47,5 +47,17 @@
|
||||
<item name="widgetLayout">@layout/preference_material_switch</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.FilterChip" parent="@style/Widget.Material3.Chip.Filter" />
|
||||
<style name="Chip.Filter" parent="@style/Widget.Material3.Chip.Filter" />
|
||||
|
||||
<style name="Chip.TopChart" parent="@style/Widget.Material3.Chip.Filter">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Aurora.Line1</item>
|
||||
<item name="chipStrokeColor">?attr/colorControlHighlight</item>
|
||||
</style>
|
||||
|
||||
<style name="Chip.Tag" parent="@style/Widget.Material3.Chip.Assist">
|
||||
<item name="android:clickable">false</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Aurora.Line2</item>
|
||||
<item name="chipBackgroundColor">?attr/colorControlHighlight</item>
|
||||
<item name="chipStrokeWidth">0dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -7,7 +7,7 @@ composeBom = "2024.11.00"
|
||||
coreVersion = "1.15.0"
|
||||
epoxyVersion = "5.1.4"
|
||||
espressoVersion = "3.6.1"
|
||||
gplayapiVersion = "3.4.3"
|
||||
gplayapiVersion = "3.4.4"
|
||||
gsonVersion = "2.11.0"
|
||||
hiddenapibypassVersion = "4.3"
|
||||
hiltVersion = "2.53"
|
||||
|
||||
Reference in New Issue
Block a user