commit dfc706b13ecebf2d6d76c35a00fd6ccd86aae04f Author: Rahul Kumar Patel Date: Mon Feb 15 04:52:56 2021 +0530 Initial v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..74b3680c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Built application files +*.apk +*.ap_ + +# Files for the Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gitlab files +.gitlab/ + +# Gradle files +.gradle/ +build/ +/*/build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# From .gitignore generated by Android Studio +*.iml +.DS_Store +/captures +/.idea +/.gradle* +gradle-app.setting +*.zip + +app/release/* +app/beta/* +app/alpha/* diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..b7eb68fc2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,166 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: "androidx.navigation.safeargs.kotlin" + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.aurora.store" + minSdkVersion 19 + targetSdkVersion 30 + + versionCode 30 + versionName "4.0.1" + + vectorDrawables.useSupportLibrary = true + multiDexEnabled true + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + zipAlignEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + beta { + initWith release + applicationIdSuffix = ".beta" + } + + debug { + applicationIdSuffix = ".debug" + } + } + + buildFeatures { + viewBinding true + } + + kotlinOptions { + jvmTarget = '1.8' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +buildscript { + ext { + versions = [ + okhttp3 : "4.9.0", + fetch2 : "3.1.6", + fuel : "2.3.0", + glide : "4.11.0", + lifecycle: "2.3.0", + epoxy : "4.3.1", + libsu : "2.5.1" + ] + } +} + +kapt { + correctErrorTypes = true +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + + //MultiDex for Kitkat support + implementation("androidx.multidex:multidex:2.0.1") + + //Protobuf + implementation("com.google.protobuf:protobuf-java:3.14.0") + + //Apache's Goodies + implementation("commons-io:commons-io:2.7") + implementation("org.apache.commons:commons-text:1.8") + + //Google's Goodies + implementation("com.google.android.material:material:1.3.0") + implementation("com.google.android:flexbox:2.0.1") + implementation("com.google.code.gson:gson:2.8.6") + + //AndroidX + implementation("androidx.core:core-ktx:1.3.2") + implementation("androidx.viewpager2:viewpager2:1.0.0") + implementation("androidx.vectordrawable:vectordrawable:1.1.0") + implementation("androidx.preference:preference-ktx:1.1.1") + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") + + //Arch LifeCycle + implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle}") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.lifecycle}") + implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${versions.lifecycle}") + + //Arch Navigation + implementation("androidx.navigation:navigation-fragment-ktx:${navigation}") + implementation("androidx.navigation:navigation-runtime-ktx:${navigation}") + implementation("androidx.navigation:navigation-ui-ktx:${navigation}") + + //UI Addons + implementation("com.github.florent37:expansionpanel:1.2.4") + + //Easy Permission + implementation("com.github.quickpermissions:quickpermissions-kotlin:0.4.0") + + //Glide + implementation("com.github.bumptech.glide:glide:${versions.glide}") + kapt("com.github.bumptech.glide:compiler:${versions.glide}") + + //Shimmer + implementation("com.facebook.shimmer:shimmer:0.5.0") + + //Epoxy + implementation("com.airbnb.android:epoxy:${versions.epoxy}") + kapt("com.airbnb.android:epoxy-processor:${versions.epoxy}") + + //Merlin + implementation("com.novoda:merlin:1.2.0") + + //HTTP Clients + implementation("com.github.kittinunf.fuel:fuel:${versions.fuel}") + implementation("com.squareup.okhttp3:okhttp:${versions.okhttp3}") + + //Fetch - Downloader + implementation "androidx.tonyodev.fetch2:xfetch2:${versions.fetch2}" + + //Kovenant + implementation("nl.komponents.kovenant:kovenant:3.3.0") + implementation("nl.komponents.kovenant:kovenant-android:3.3.0") + + //EventBus + implementation("org.greenrobot:eventbus:3.2.0") + + //Lib-SU + implementation "com.github.topjohnwu.libsu:core:${versions.libsu}" + + //Love <3 + api("com.gitlab.AuroraOSS:gplayapi:514f061739") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..a3bba898e --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,120 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +# ServiceLoader support +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater +-keepclassmembernames class kotlin.coroutines.SafeContinuation { + volatile ; +} + +# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when +# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used. +-dontwarn java.lang.instrument.ClassFileTransformer +-dontwarn sun.misc.SignalHandler +-dontwarn java.lang.instrument.Instrumentation +-dontwarn sun.misc.Signal + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform +-dontwarn org.conscrypt.ConscryptHostnameVerifier + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and +# EnclosingMethod is required to use InnerClasses. +-keepattributes Signature, InnerClasses, EnclosingMethod + +# Retrofit does reflection on method and parameter annotations. +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations + +# Retain service method parameters when optimizing. +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} + +# Ignore annotation used for build tooling. +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement + +# Ignore JSR 305 annotations for embedding nullability information. +-dontwarn javax.annotation.** + +# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. +-dontwarn kotlin.Unit + +# Top-level functions that can only be used by Kotlin. +-dontwarn retrofit2.KotlinExtensions +-dontwarn retrofit2.KotlinExtensions$* + +#Kovenant +-dontwarn rx.internal.util.unsafe.** +-dontwarn nl.komponents.kovenant.unsafe.** + +-dontwarn okio.** +-keep class com.google.** +-dontwarn com.google.** +-keep class com.google.gson.Gson {*;} + +# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy +# and replaces all potential values with null. Explicitly keeping the interfaces prevents this. +-if interface * { @retrofit2.http.* ; } +-keep,allowobfuscation interface <1> + +#Event Bus +-keepattributes *Annotation* +-keepclassmembers class * { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { + (java.lang.Throwable); +} + +-keepclassmembers enum * { *; } +-keep class com.aurora.store.view.ui.preferences.** +-dontwarn com.aurora.store.view.ui.preferences.** + +-keepclassmembers class * { + private ; +} + +#GPlay API +-keep public class com.aurora.gplayapi.** { + *; +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..070d4c90f --- /dev/null +++ b/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt @@ -0,0 +1,41 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.aurora.aurorastore", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..84fbacb63 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl b/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl new file mode 100644 index 000000000..4b4b71b2e --- /dev/null +++ b/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015-2016 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.aurora.services; + +interface IPrivilegedCallback { + void handleResult( + in String packageName, + in int returnCode + ); +} \ No newline at end of file diff --git a/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl b/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl new file mode 100644 index 000000000..2c1cc63ff --- /dev/null +++ b/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015-2016 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.aurora.services; + +import com.aurora.services.IPrivilegedCallback; + +interface IPrivilegedService { + + boolean hasPrivilegedPermissions(); + + oneway void installPackage( + in String packageName, + in Uri uri, + in int flags, + in String installerPackageName, + in IPrivilegedCallback callback + ); + + oneway void installSplitPackage( + in String packageName, + in List uriList, + in int flags, + in String installerPackageName, + in IPrivilegedCallback callback + ); + + oneway void deletePackage( + in String packageName, + in int flags, + in IPrivilegedCallback callback + ); +} \ No newline at end of file diff --git a/app/src/main/assets/accent.json b/app/src/main/assets/accent.json new file mode 100755 index 000000000..f8d88bdce --- /dev/null +++ b/app/src/main/assets/accent.json @@ -0,0 +1,50 @@ +[ + { + "id": "1", + "accent": "#6C63FF" + }, + { + "id": "2", + "accent": "#FF00FF" + }, + { + "id": "3", + "accent": "#F50057" + }, + { + "id": "4", + "accent": "#F9A826" + }, + { + "id": "5", + "accent": "#49A942" + }, + { + "id": "6", + "accent": "#6633cc" + }, + { + "id": "7", + "accent": "#52565e" + }, + { + "id": "8", + "accent": "#ee70a6" + }, + { + "id": "9", + "accent": "#b5c327" + }, + { + "id": "10", + "accent": "#f38654" + }, + { + "id": "11", + "accent": "#61A7E0" + }, + { + "id": "12", + "accent": "#7289da" + } +] \ No newline at end of file diff --git a/app/src/main/assets/dash.json b/app/src/main/assets/dash.json new file mode 100755 index 000000000..7a774f7f3 --- /dev/null +++ b/app/src/main/assets/dash.json @@ -0,0 +1,37 @@ +[ + { + "id": "0", + "title": "FAQs", + "subtitle": "Have questions? Find out the answers.", + "icon": "ic_faq", + "url": "https://gitlab.com/AuroraOSS/AuroraStore" + }, + { + "id": "1", + "title": "Source code", + "icon": "ic_code", + "subtitle": "Find out what's inside.", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/blob/master" + }, + { + "id": "2", + "title": "License", + "subtitle": "Yes we do have one ;)", + "icon": "ic_license", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/LICENSE" + }, + { + "id": "3", + "title": "Privacy", + "subtitle": "Find out if we have your nudes :p", + "icon": "ic_privacy", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/PRIVACY" + }, + { + "id": "4", + "title": "Disclaimer", + "subtitle": "Hold your own beer!", + "icon": "ic_disclaimer", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/DISCLAIMER" + } +] \ No newline at end of file diff --git a/app/src/main/assets/exodus_trackers.json b/app/src/main/assets/exodus_trackers.json new file mode 100755 index 000000000..7552ebd1b --- /dev/null +++ b/app/src/main/assets/exodus_trackers.json @@ -0,0 +1,2724 @@ +[ + { + "1": { + "id": 1, + "name": "Teemo", + "creation_date": "2017-09-24", + "code_signature": "com.databerries.|com.geolocstation.", + "network_signature": "databerries\\.com", + "website": "https://www.teemo.co" + }, + "2": { + "id": 2, + "name": "FidZup", + "creation_date": "2017-09-24", + "code_signature": "com.fidzup.", + "network_signature": "fidzup", + "website": "https://www.fidzup.com" + }, + "3": { + "id": 3, + "name": "Audience Studio (Krux)", + "creation_date": "2017-09-24", + "code_signature": "com.krux.androidsdk", + "network_signature": "krxd\\.net", + "website": "https://www.salesforce.com/products/marketing-cloud/data-management/?mc=DMP" + }, + "4": { + "id": 4, + "name": "Ad4Screen", + "creation_date": "2017-09-24", + "code_signature": "com.ad4screen.sdk", + "network_signature": "a4\\.tl|accengage\\.com|ad4push\\.com|ad4screen\\.com", + "website": "http://www.ad4screen.com" + }, + "5": { + "id": 5, + "name": "Google DoubleClick", + "creation_date": "2017-09-24", + "code_signature": "", + "network_signature": "2mdn\\.net|dmtry\\.com|doubleclick\\.com|doubleclick\\.net|mng-ads\\.com", + "website": "https://www.doubleclickbygoogle.com/" + }, + "6": { + "id": 6, + "name": "Weborama", + "creation_date": "2017-09-24", + "code_signature": "com.weborama.", + "network_signature": "weborama\\.fr|weborama\\.net", + "website": "https://www.weborama.com" + }, + "7": { + "id": 7, + "name": "Smart", + "creation_date": "2017-09-24", + "code_signature": "com.smartadserver.", + "network_signature": "adsrvr\\.org|akamai\\.smartadserver\\.com|cdn1\\.smartadserver\\.com|diff2\\.smartadserver\\.com|diff3\\.smartadserver\\.com|diff\\.smartadserver\\.com|eqx\\.smartadserver\\.com|gallery\\.smartadserver\\.com|im2\\.smartadserver\\.com|insight\\.adsrvr\\.org|itx5-publicidad\\.smartadserver\\.com|itx5\\.smartadserver\\.com|js\\.adsrvr\\.org|match\\.adsrvr\\.org|preview\\.smartadserver\\.com|rtb-csync\\.smartadserver\\.com|saspreview\\.com|smartadserver\\.com|smartadserver\\.ru|tmk\\.smartadserver\\.com|usw\\-lax\\.adsrvr\\.org", + "website": "http://smartadserver.com/" + }, + "8": { + "id": 8, + "name": "JW Player", + "creation_date": "2017-09-24", + "code_signature": "com.longtailvideo.jwplayer.", + "network_signature": "g\\.jwpsrv\\.com|jwpltx\\.com|p\\.jwpcdn\\.com", + "website": "https://jwplayer.com" + }, + "9": { + "id": 9, + "name": "Loggly", + "creation_date": "2017-09-24", + "code_signature": "com.github.tony19.timber.loggly|com.github.tony19.loggly|com.visiware.sync2ad.logger.loggly.", + "network_signature": "loggly\\.com", + "website": "http://loggly.com/" + }, + "10": { + "id": 10, + "name": "Xiti", + "creation_date": "2017-09-24", + "code_signature": "", + "network_signature": "", + "website": "http://Xiti.com" + }, + "11": { + "id": 11, + "name": "OutBrain", + "creation_date": "2017-09-24", + "code_signature": "com.outbrain.", + "network_signature": "outbrain\\.com", + "website": "http://www.outbrain.com/" + }, + "12": { + "id": 12, + "name": "AppsFlyer", + "creation_date": "2017-09-24", + "code_signature": "com.appsflyer.", + "network_signature": "appsflyer\\.com", + "website": "http://AppsFlyer.com" + }, + "13": { + "id": 13, + "name": "Ligatus", + "creation_date": "2017-09-24", + "code_signature": ".LigatusManager|.LigatusViewClient|com.ligatus.android.adframework", + "network_signature": "ligatus\\.com", + "website": "http://ligatus.com" + }, + "14": { + "id": 14, + "name": "Widespace", + "creation_date": "2017-09-24", + "code_signature": "com.widespace.", + "network_signature": "widespace\\.com", + "website": "http://widespace.com" + }, + "15": { + "id": 15, + "name": "AppNexus", + "creation_date": "2017-09-24", + "code_signature": "com.appnexus.opensdk.", + "network_signature": "247realmedia\\.com|adnxs\\.com|appnexus\\.com|appnexus\\.net", + "website": "https://www.appnexus.com/" + }, + "16": { + "id": 16, + "name": "Localytics", + "creation_date": "2017-09-24", + "code_signature": "com.localytics.android.", + "network_signature": "analytics\\.localytics\\.com|manifest\\.localytics\\.com|profile\\.localytics\\.com|sdk-assets\\.localytics\\.com", + "website": "http://localytics.com" + }, + "17": { + "id": 17, + "name": "Braze (formerly Appboy)", + "creation_date": "2017-09-24", + "code_signature": "com.appboy", + "network_signature": "appboy\\.com", + "website": "https://www.braze.com" + }, + "18": { + "id": 18, + "name": "mParticle", + "creation_date": "2017-09-24", + "code_signature": "com.mparticle", + "network_signature": "mparticle\\.com", + "website": "http://mparticle.com" + }, + "20": { + "id": 20, + "name": "S4M", + "creation_date": "2017-09-24", + "code_signature": "com.sam4mobile.", + "network_signature": "s4m\\.io|sam4m\\.com", + "website": "http://www.s4m.io/" + }, + "22": { + "id": 22, + "name": "Sizmek", + "creation_date": "2017-09-24", + "code_signature": ".sizmek.", + "network_signature": "serving-sys\\.com", + "website": "https://www.sizmek.com" + }, + "23": { + "id": 23, + "name": "Batch", + "creation_date": "2017-09-24", + "code_signature": "com.batch.android.", + "network_signature": "batch\\.com", + "website": "https://batch.com" + }, + "24": { + "id": 24, + "name": "Sync2Ad", + "creation_date": "2017-09-24", + "code_signature": "com.visiware.sync2ad.dmp.", + "network_signature": "sync2ad\\.com", + "website": "https://www.sync2ad.com/" + }, + "25": { + "id": 25, + "name": "Flurry", + "creation_date": "2017-09-24", + "code_signature": "com.flurry.", + "network_signature": "flurry\\.com", + "website": "http://www.flurry.com" + }, + "26": { + "id": 26, + "name": "HockeyApp", + "creation_date": "2017-09-24", + "code_signature": "net.hockeyapp.", + "network_signature": "hockeyapp\\.net", + "website": "http://hockeyapp.net" + }, + "27": { + "id": 27, + "name": "Google CrashLytics", + "creation_date": "2017-09-24", + "code_signature": "io.fabric.|com.crashlytics.|com.google.firebase.crashlytics", + "network_signature": "crashlytics\\.com", + "website": "http://crashlytics.com" + }, + "28": { + "id": 28, + "name": "LeanPlum", + "creation_date": "2017-09-24", + "code_signature": "com.leanplum.", + "network_signature": "leanplum\\.com", + "website": "https://www.leanplum.com/" + }, + "29": { + "id": 29, + "name": "Tinder Analytics", + "creation_date": "2017-09-24", + "code_signature": "com.tinder.analytics|com.tinder.ads", + "network_signature": "etl\\.tindersparks\\.com", + "website": "http://tinder.com" + }, + "30": { + "id": 30, + "name": "Schibsted", + "creation_date": "2017-09-25", + "code_signature": ".schibsted.", + "network_signature": "schibsted\\.com|schibsted\\.io", + "website": "http://www.schibsted.com/en/ir/" + }, + "31": { + "id": 31, + "name": "ATInternet", + "creation_date": "2017-09-25", + "code_signature": "com.atinternet.", + "network_signature": "ati-host\\.net", + "website": "https://www.atinternet.com/en/" + }, + "32": { + "id": 32, + "name": "Tealium", + "creation_date": "2017-09-25", + "code_signature": ".tealium.", + "network_signature": "tealiumiq\\.com|tiqcdn\\.com", + "website": "https://tealium.com/" + }, + "33": { + "id": 33, + "name": "Nexage", + "creation_date": "2017-09-27", + "code_signature": "com.nexage.android.|org.nexage.", + "network_signature": "nexage\\.com", + "website": "http://nexage.com/" + }, + "34": { + "id": 34, + "name": "Ogury Presage", + "creation_date": "2017-09-27", + "code_signature": "io.presage.", + "network_signature": "presage\\.io", + "website": "http://www.presage.io/" + }, + "35": { + "id": 35, + "name": "Twitter MoPub", + "creation_date": "2017-09-27", + "code_signature": "com.mopub.mobileads.|com.mopub.nativeads.", + "network_signature": "mopub\\.com", + "website": "https://www.mopub.com/" + }, + "36": { + "id": 36, + "name": "Add Apt Tr", + "creation_date": "2017-09-27", + "code_signature": "com.intentsoftware.addapptr.", + "network_signature": "aatkit\\.com", + "website": "https://www.addapptr.com" + }, + "37": { + "id": 37, + "name": "Vectaury", + "creation_date": "2017-09-28", + "code_signature": "io.vectaury.", + "network_signature": "vectaury\\.io", + "website": "http://vectaury.io/" + }, + "38": { + "id": 38, + "name": "Tune", + "creation_date": "2017-11-02", + "code_signature": "com.tune|com.mobileapptracker", + "network_signature": "mobileapptracking\\.com", + "website": "https://www.tune.com" + }, + "39": { + "id": 39, + "name": "Pushwoosh", + "creation_date": "2017-11-12", + "code_signature": "com.pushwoosh", + "network_signature": "pushwoosh\\.com", + "website": "https://www.pushwoosh.com/" + }, + "40": { + "id": 40, + "name": "Demdex", + "creation_date": "2017-11-15", + "code_signature": "com.adobe.mobile.Analytics", + "network_signature": "demdex\\.net", + "website": "https://www.adobe.com/data-analytics-cloud/audience-manager.html" + }, + "41": { + "id": 41, + "name": "AdsWizz", + "creation_date": "2017-11-15", + "code_signature": ".adswizz.", + "network_signature": "adswizz\\.com|cdn\\.adswizz.com\\.edgesuite\\.net", + "website": "http://www.adswizz.com/" + }, + "42": { + "id": 42, + "name": "ExactTarget", + "creation_date": "2017-11-16", + "code_signature": "com.exacttarget.", + "network_signature": "\\.exacttarget\\.", + "website": "http://help.exacttarget.com/en/technical_library/API_Overview/" + }, + "43": { + "id": 43, + "name": "Omniture", + "creation_date": "2017-11-16", + "code_signature": "com.omniture.|com.adobe.adms.measurement.", + "network_signature": "omniture\\.com|omtrdc\\.net", + "website": "https://www.adobe.com/analytics/adobe-analytics-features.html" + }, + "44": { + "id": 44, + "name": "OpenLocate", + "creation_date": "2017-11-16", + "code_signature": "com.safegraph.|com.openlocate", + "network_signature": "api\\.safegraph\\.com", + "website": "https://www.safegraph.com/" + }, + "45": { + "id": 45, + "name": "TagCommander (Commanders Act.)", + "creation_date": "2017-11-28", + "code_signature": "com.tagcommander.", + "network_signature": "\\.commander1\\.com|\\.tagcommander.com", + "website": "https://www.commandersact.com/" + }, + "46": { + "id": 46, + "name": "CrowdTangle", + "creation_date": "2017-12-03", + "code_signature": "", + "network_signature": "api\\.crowdtangle\\.com", + "website": "https://www.crowdtangle.com/" + }, + "47": { + "id": 47, + "name": "Facebook Audience", + "creation_date": "2017-12-03", + "code_signature": "com.facebook.audiencenetwork", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android/" + }, + "48": { + "id": 48, + "name": "Google Analytics", + "creation_date": "2017-12-03", + "code_signature": "com.google.android.apps.analytics.|com.google.android.gms.analytics.", + "network_signature": "google-analytics\\.com", + "website": "http://www.google.com/analytics/" + }, + "49": { + "id": 49, + "name": "Google Firebase Analytics", + "creation_date": "2017-12-03", + "code_signature": "com.google.firebase.analytics.|com.google.android.gms.measurement.", + "network_signature": "firebase\\.com", + "website": "https://firebase.google.com/" + }, + "51": { + "id": 51, + "name": "Eulerian", + "creation_date": "2017-12-03", + "code_signature": "com.eulerian.android.sdk", + "network_signature": "eulerian\\.com", + "website": "https://www.eulerian.com/en/" + }, + "52": { + "id": 52, + "name": "Adjust", + "creation_date": "2017-12-03", + "code_signature": "com.adjust.sdk.", + "network_signature": "adj\\.st|adjust\\.com", + "website": "https://www.adjust.com/" + }, + "53": { + "id": 53, + "name": "ChartBoost", + "creation_date": "2017-12-03", + "code_signature": "com.chartboost.sdk.", + "network_signature": "\\.chartboost\\.com", + "website": "https://answers.chartboost.com/en-us/" + }, + "54": { + "id": 54, + "name": "Backelite", + "creation_date": "2017-12-03", + "code_signature": "com.backelite.android.|com.backelite.bkdroid.", + "network_signature": "backelite\\.com", + "website": "https://www.backelite.com/" + }, + "55": { + "id": 55, + "name": "Areametrics", + "creation_date": "2017-12-03", + "code_signature": "com.areametrics.areametricssdk|com.areametrics.nosdkandroid", + "network_signature": "areametrics\\.com", + "website": "https://areametrics.com/" + }, + "56": { + "id": 56, + "name": "ComScore", + "creation_date": "2017-12-03", + "code_signature": "com.comscore.", + "network_signature": "comscore\\.com", + "website": "https://comscore.com/" + }, + "57": { + "id": 57, + "name": "Cuebiq", + "creation_date": "2017-12-03", + "code_signature": "com.cuebiq.cuebiqsdk.model.Collector|com.cuebiq.cuebiqsdk.receiver.CoverageReceiver", + "network_signature": "cuebiq\\.com", + "website": "http://www.cuebiq.com/" + }, + "58": { + "id": 58, + "name": "HelpShift", + "creation_date": "2017-12-03", + "code_signature": "com.helpshift", + "network_signature": "helpshift\\.com", + "website": "https://www.helpshift.com" + }, + "59": { + "id": 59, + "name": "Kontakt", + "creation_date": "2017-12-03", + "code_signature": "com.kontakt.sdk.android.", + "network_signature": "kontakt\\.io", + "website": "https://kontakt.io/" + }, + "60": { + "id": 60, + "name": "Locuslabs", + "creation_date": "2017-12-03", + "code_signature": "com.locuslabs.sdk", + "network_signature": "locuslabs\\.com", + "website": "http://locuslabs.com" + }, + "61": { + "id": 61, + "name": "Moat", + "creation_date": "2017-12-03", + "code_signature": "com.moat.analytics.mobile.", + "network_signature": "apx\\.moatads\\.com|geo\\.moatads\\.com|js\\.moatads\\.com|mb\\.moatads\\.com|moat\\.com|pixel\\.moatads\\.com|px\\.moatads\\.com|sejs\\.moatads\\.com|yt\\.moatads\\.com|yts\\.moatads\\.com|z\\.moatads\\.com", + "website": "https://moat.com/analytics" + }, + "62": { + "id": 62, + "name": "Segment", + "creation_date": "2017-12-03", + "code_signature": "com.segment.analytics.", + "network_signature": "api\\.segment\\.io|segment\\.com", + "website": "https://segment.com/" + }, + "63": { + "id": 63, + "name": "Mobile Engagement", + "creation_date": "2017-12-04", + "code_signature": "com.ubikod.capptain.|com.microsoft.azure.engagement.", + "network_signature": "login\\.microsoftonline\\.com|management\\.azure\\.com", + "website": "https://docs.microsoft.com/en-us/azure/mobile-engagement/mobile-engagement-android-sdk-overview" + }, + "64": { + "id": 64, + "name": "Colocator", + "creation_date": "2017-12-04", + "code_signature": "net.crowdconnected.androidcolocator", + "network_signature": "colocator\\.net", + "website": "https://developers.colocator.net" + }, + "65": { + "id": 65, + "name": "Facebook Ads", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.ads", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "66": { + "id": 66, + "name": "Facebook Analytics", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.appevents", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "67": { + "id": 67, + "name": "Facebook Login", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.login", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "68": { + "id": 68, + "name": "Facebook Notifications", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.notifications", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "69": { + "id": 69, + "name": "Facebook Places", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.places", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "70": { + "id": 70, + "name": "Facebook Share", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.share", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "71": { + "id": 71, + "name": "Google Ads", + "creation_date": "2017-12-05", + "code_signature": "", + "network_signature": "\\.google\\.com", + "website": "https://developers.google.com/admob/android" + }, + "72": { + "id": 72, + "name": "AppLovin (MAX and SparkLabs)", + "creation_date": "2018-01-05", + "code_signature": "com.applovin", + "network_signature": "applovin\\.com|applvn\\.com", + "website": "https://www.applovin.com/" + }, + "73": { + "id": 73, + "name": "Glispa Connect (Formerly Avocarrot)", + "creation_date": "2018-01-05", + "code_signature": "com.avocarrot.sdk", + "network_signature": "\\.avocarrot\\.com|ads\\.glispa\\.com|exp\\.glispa\\.com|rtb\\.platform\\.glispa\\.com|templates\\.glispaconnect\\.com|trk\\.glispa\\.com", + "website": "https://www.glispa.com" + }, + "74": { + "id": 74, + "name": "NativeX", + "creation_date": "2018-01-05", + "code_signature": "com.nativex", + "network_signature": "nativex\\.com", + "website": "http://www.nativex.com/" + }, + "75": { + "id": 75, + "name": "Baidu Maps", + "creation_date": "2018-01-05", + "code_signature": "com.baidu.BaiduMap", + "network_signature": "map\\.baidu\\.com", + "website": "https://map.baidu.com" + }, + "76": { + "id": 76, + "name": "WeChat Location", + "creation_date": "2018-01-05", + "code_signature": "com.tencent.map.geolocation|com.tencent.mm.plugin.location.|com.tencent.mm.plugin.location_soso.|com.tencent.mm.plugin.location_google", + "network_signature": "map\\.qq\\.com", + "website": "https://wechat.com" + }, + "77": { + "id": 77, + "name": "HyperTrack", + "creation_date": "2018-01-05", + "code_signature": "com.hypertrack|com.hypertracklive.|io.hypertrack", + "network_signature": "api\\.hypertrack\\.com|hypertrack\\.amazonaws\\.com|trck\\.at", + "website": "http://hypertrack.com" + }, + "78": { + "id": 78, + "name": "Uber Analytics", + "creation_date": "2018-01-05", + "code_signature": "com.ubercab.analytics.|com.ubercab.library.metrics.analytics.|com.ubercab.client.core.analytics.", + "network_signature": "events\\.uber\\.com", + "website": "https://uber.com" + }, + "79": { + "id": 79, + "name": "Lisnr", + "creation_date": "2018-01-05", + "code_signature": "com.lisnr.", + "network_signature": "lisnr\\.com", + "website": "http://lisnr.com" + }, + "80": { + "id": 80, + "name": "SilverPush", + "creation_date": "2018-01-05", + "code_signature": "com.silverpush.|com.silverpush.location|com.silverpush.sdk.android.SPService", + "network_signature": "54\\.243\\.73\\.253:8080/SilverPush/|silverpush\\.co|silverpush\\.com", + "website": "http://silverpush.co" + }, + "81": { + "id": 81, + "name": "Shopkick", + "creation_date": "2018-01-05", + "code_signature": "com.shopkick.sdk.api.|com.shopkick.fetchers.", + "network_signature": "sdk\\.shopkick\\.com|shopkick\\.com|shopkick\\.de", + "website": "https://shopkick.com" + }, + "82": { + "id": 82, + "name": "Alphonso", + "creation_date": "2018-01-05", + "code_signature": "tv.alphonso.service", + "network_signature": "api\\.alphonso\\.tv|prov\\.alphonso\\.tv", + "website": "http://alphonso.tv" + }, + "83": { + "id": 83, + "name": "Smaato", + "creation_date": "2018-01-05", + "code_signature": "com.smaato.soma.", + "network_signature": "smaato\\.net|soma\\.smaato\\.net", + "website": "https://smaato.com" + }, + "84": { + "id": 84, + "name": "Scandit", + "creation_date": "2018-01-05", + "code_signature": "com.scandit.", + "network_signature": "scandit\\.com", + "website": "https://scandit.com" + }, + "85": { + "id": 85, + "name": "Inrix", + "creation_date": "2018-01-06", + "code_signature": "com.inrix.sdk", + "network_signature": "inrix\\.com|inrix\\.io", + "website": "http://inrix.com/" + }, + "86": { + "id": 86, + "name": "Signal360", + "creation_date": "2018-01-13", + "code_signature": "com.signal360.sdk.core.|com.sonicnotify.sdk.core.|com.rnsignal360", + "network_signature": "signal360\\.com|sonicnotify\\.com", + "website": "http://www.signal360.com" + }, + "87": { + "id": 87, + "name": "TeleQuid", + "creation_date": "2018-03-04", + "code_signature": "com.telequid.", + "network_signature": "mars\\.telequid\\.com", + "website": "http://www.telequid.com/" + }, + "88": { + "id": 88, + "name": "Retency", + "creation_date": "2018-03-04", + "code_signature": "com.retency.sdk.android", + "network_signature": "", + "website": "http://retency.com" + }, + "89": { + "id": 89, + "name": "MAdvertise", + "creation_date": "2018-03-04", + "code_signature": "com.mngads.sdk|com.mngads.views|com.mngads.", + "network_signature": "dispatcher\\.mng\\-ads\\.com|mobile\\.mng\\-ads\\.com", + "website": "http://madvertise.com" + }, + "90": { + "id": 90, + "name": "AdColony", + "creation_date": "2018-03-04", + "code_signature": "com.adcolony", + "network_signature": "adc3-launch\\.adcolony\\.com|adcolony\\.com|ads30\\.adcolony\\.com|androidads20\\.adcolony\\.com|androidads21\\.adcolony\\.com|androidads23\\.adcolony\\.com|events3alt\\.adcolony\\.com|wd\\.adcolony\\.com", + "website": "http://adcolony.com/" + }, + "91": { + "id": 91, + "name": "AccountKit", + "creation_date": "2018-03-04", + "code_signature": "com.facebook.accountkit", + "network_signature": "graph\\.accountkit\\.com", + "website": "https://www.accountkit.com/" + }, + "92": { + "id": 92, + "name": "Amazon Advertisement", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.device.ads", + "network_signature": "", + "website": "https://developer.amazon.com/public/apis/earn/mobile-ads/docs/quick-start" + }, + "93": { + "id": 93, + "name": "Amazon Mobile Associates", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.device.associates", + "network_signature": "", + "website": "https://developer.amazon.com/mobile-associates" + }, + "94": { + "id": 94, + "name": "Radius Networks", + "creation_date": "2018-03-04", + "code_signature": "com.radiusnetworks", + "network_signature": "proximitykit\\.radiusnetworks\\.com", + "website": "https://www.radiusnetworks.com/" + }, + "95": { + "id": 95, + "name": "Amazon Analytics (Amazon insights)", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.insights|com.amazonaws.mobileconnectors.pinpoint.analytics.|com.amazonaws.mobileconnectors.amazonmobileanalytics", + "network_signature": "mobileanalytics\\.us-east-1\\.amazonaws\\.com", + "website": "https://developer.amazon.com/docs/apps-and-games/sdk-downloads.html" + }, + "96": { + "id": 96, + "name": "Baidu APPX", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.appx", + "network_signature": "", + "website": "https://app.baidu.com/" + }, + "97": { + "id": 97, + "name": "Baidu Location", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.location", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "99": { + "id": 99, + "name": "Baidu Map", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mapapi", + "network_signature": "", + "website": "http://lbsyun.baidu.com/" + }, + "100": { + "id": 100, + "name": "Baidu Mobile Ads", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mobads", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "101": { + "id": 101, + "name": "Baidu Mobile Stat", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mobstat", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "102": { + "id": 102, + "name": "Estimote", + "creation_date": "2018-03-04", + "code_signature": "com.estimote.", + "network_signature": ".*\\.estimote\\.com", + "website": "https://estimote.com/" + }, + "103": { + "id": 103, + "name": "Baidu Navigation", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.navi", + "network_signature": "", + "website": "http://lbsyun.baidu.com/index.php?title=android-navsdk" + }, + "104": { + "id": 104, + "name": "Fyber", + "creation_date": "2018-03-04", + "code_signature": "com.fyber.", + "network_signature": "adproxy\\.fyber\\.com|appengage-video\\.fyber\\.com|banner\\.fyber\\.com|engine\\.fyber\\.com|interstitial\\.fyber\\.com|mbe-cdn\\.fyber\\.com|offer\\.fyber\\.com|service\\.fyber\\.com|tracker\\.fyber\\.com|video-interstitial-assets-cdn\\.fyber\\.com|video\\.fyber\\.com", + "website": "https://www.fyber.com/" + }, + "105": { + "id": 105, + "name": "Google Tag Manager", + "creation_date": "2018-03-04", + "code_signature": "com.google.tagmanager|com.google.android.gms.tagmanager", + "network_signature": "www\\.googletagmanager\\.com|www\\.googletagservices\\.com", + "website": "https://www.google.com/analytics/tag-manager/" + }, + "106": { + "id": 106, + "name": "Inmobi", + "creation_date": "2018-03-04", + "code_signature": "com.inmobi", + "network_signature": "c\\.w\\.inmobi\\.com|china\\.inmobi\\.com|config-ltvp\\.inmobi\\.com|config\\.inmobi\\.com|et\\.w\\.inmobi\\.com|i\\.l\\.inmobicdn\\.net|i\\.w\\.inmobi\\.com|inmobi\\.cn|inmobi\\.com|inmobi\\.info|inmobi\\.net|inmobi\\.us|inmobicdn\\.com|inmobicdn\\.net|inmobisdk\\-a\\.akamaihd\\.net|japan\\.inmobi\\.com|r\\.w\\.inmobi\\.com|sdkm\\.w\\.inmobi\\.com|sdktm\\.w\\.inmobi\\.com|w\\.inmobi\\.com", + "website": "http://inmobi.com" + }, + "107": { + "id": 107, + "name": "Millennial Media", + "creation_date": "2018-03-04", + "code_signature": "com.millennialmedia.", + "network_signature": "adtech\\.de|contextual\\.media\\.net|media\\.net|millennialmedia\\.com", + "website": "https://www.millennialmedia.com/" + }, + "108": { + "id": 108, + "name": "Snowplow", + "creation_date": "2018-03-04", + "code_signature": "com.snowplowanalytics.", + "network_signature": "", + "website": "https://snowplowanalytics.com/" + }, + "109": { + "id": 109, + "name": "Fyber SponsorPay", + "creation_date": "2018-03-04", + "code_signature": "com.sponsorpay", + "network_signature": "appengage-video\\.sponsorpay\\.com|cdn1\\.sponsorpay\\.com|cdn2\\.sponsorpay\\.com|cdn3\\.sponsorpay\\.com|cdn4\\.sponsorpay\\.com|engine\\.sponsorpay\\.com", + "website": "http://www.sponsorpay.com" + }, + "110": { + "id": 110, + "name": "Supersonic Ads", + "creation_date": "2018-03-04", + "code_signature": "com.supersonic.adapters.supersonicads|com.supersonicads.sdk", + "network_signature": "click-haproxy\\.supersonicads\\.com|cx\\.ssacdn\\.com|init\\.supersonicads\\.com|logs\\.supersonic\\.com|outcome\\.supersonicads\\.com|ow-gateway\\.supersonicads\\.com|pixel-tracking\\.sonic-us\\.supersonicads\\.com|rv-gateway\\.supersonicads\\.com|static\\.ssacdn\\.com|supersonic\\.com|supersonicads-a\\.akamaihd\\.net|tag\\-mediation.supersonic.com|ua\\.supersonicads\\.com|v\\.ssacdn\\.com|www\\.supersonicads\\.com", + "website": "https://www.supersonic.com/" + }, + "111": { + "id": 111, + "name": "Carnival", + "creation_date": "2018-03-04", + "code_signature": "com.carnival.sdk|com.carnivalmobile", + "network_signature": "devices\\.carnivalmobile\\.com", + "website": "http://carnival.io/" + }, + "112": { + "id": 112, + "name": "Tencent Map LBS", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.lbs", + "network_signature": "", + "website": "https://lbs.qq.com/" + }, + "113": { + "id": 113, + "name": "Tencent MobWin", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.mobwin", + "network_signature": "", + "website": "https://www.tencent.com/en-us/" + }, + "114": { + "id": 114, + "name": "Tencent MTA", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.mta", + "network_signature": "", + "website": "https://mta.qq.com/" + }, + "115": { + "id": 115, + "name": "Apptentive", + "creation_date": "2018-03-04", + "code_signature": "com.apptentive.", + "network_signature": "api\\.apptentive\\.com", + "website": "https://www.apptentive.com/" + }, + "116": { + "id": 116, + "name": "Tencent Stats", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.stat|com.tencent.wxop.stat", + "network_signature": "", + "website": "http://stat.qq.com/" + }, + "117": { + "id": 117, + "name": "Tencent Weiyun", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.weiyun", + "network_signature": "", + "website": "https://www.weiyun.com" + }, + "118": { + "id": 118, + "name": "MixPanel", + "creation_date": "2018-03-04", + "code_signature": "com.mixpanel.", + "network_signature": "api\\.mixpanel\\.com|decide\\.mixpanel\\.com|mixpanel\\.com|switchboard\\.mixpanel\\.com", + "website": "https://mixpanel.com/" + }, + "119": { + "id": 119, + "name": "Umeng Analytics", + "creation_date": "2018-03-04", + "code_signature": "com.umeng.analytics", + "network_signature": "alog\\.umeng\\.com|alogs\\.umeng\\.com|ar\\.umeng\\.com|oc\\.umeng\\.com|umeng\\.com|uop\\.umeng\\.com", + "website": "https://www.umeng.com/analytics" + }, + "120": { + "id": 120, + "name": "Umeng Feedback", + "creation_date": "2018-03-04", + "code_signature": "com.umeng.fb", + "network_signature": "alog\\.umeng\\.com|alogs\\.umeng\\.com|ar\\.umeng\\.com|oc\\.umeng\\.com|umeng\\.com|uop\\.umeng\\.com", + "website": "http://dev.umeng.com/feedback" + }, + "121": { + "id": 121, + "name": "Unity3d Ads", + "creation_date": "2018-03-04", + "code_signature": "com.unity3d.services|com.unity3d.ads", + "network_signature": "adserver\\.unityads\\.unity3d\\.com|analytics\\.social\\.unity\\.com|api\\.uca\\.cloud\\.unity3d\\.com|auction\\.unityads\\.unity3d\\.com|cdn-highwinds\\.unityads\\.unity3d\\.com|cdn\\.unityads\\.unity3d\\.com|config\\.uca\\.cloud\\.unity3d\\.com|config\\.unityads\\.unity3d\\.com|stats\\.unity3d\\.com|webview\\.unityads\\.unity3d\\.com", + "website": "https://unity3d.com/" + }, + "122": { + "id": 122, + "name": "Countly", + "creation_date": "2018-03-04", + "code_signature": "ly.count.android.", + "network_signature": "", + "website": "https://count.ly/" + }, + "123": { + "id": 123, + "name": "Urbanairship", + "creation_date": "2018-03-04", + "code_signature": "com.urbanairship", + "network_signature": "device-api\\.urbanairship\\.com|urbanairship\\.com", + "website": "https://www.urbanairship.com/" + }, + "124": { + "id": 124, + "name": "Yandex Ad", + "creation_date": "2018-03-04", + "code_signature": "com.yandex.mobile.ads", + "network_signature": "analytics\\.mobile\\.yandex\\.net|appmetrica\\.yandex\\.com|banners-slb\\.mobile\\.yandex\\.net|banners\\.mobile\\.yandex\\.net|mc\\.yandex\\.ru|report\\.appmetrica\\.yandex\\.net|startup\\.mobile\\.yandex\\.net", + "website": "https://www.yandex.com/" + }, + "125": { + "id": 125, + "name": "Amplitude", + "creation_date": "2018-03-04", + "code_signature": "com.amplitude.", + "network_signature": "amplitude\\.com|api\\.amplitude\\.com", + "website": "http://www.amplitude.com" + }, + "126": { + "id": 126, + "name": "AppSee", + "creation_date": "2018-03-04", + "code_signature": "com.appsee.", + "network_signature": "api\\.appsee\\.com", + "website": "https://www.appsee.com/" + }, + "127": { + "id": 127, + "name": "Kochava", + "creation_date": "2018-03-04", + "code_signature": "com.kochava.base.Tracker|com.kochava.android.tracker.", + "network_signature": "control\\.kochava\\.com|kvinit\\-prod\\.api\\.kochava\\.com", + "website": "https://www.kochava.com/" + }, + "129": { + "id": 129, + "name": "Webtrends", + "creation_date": "2018-03-04", + "code_signature": "com.webtrends.mobile.analytics.|com.webtrends.mobile.android", + "network_signature": "dc\\.webtrends\\.com|webtrends\\.com", + "website": "https://www.webtrends.com/" + }, + "130": { + "id": 130, + "name": "New Relic", + "creation_date": "2018-03-04", + "code_signature": "com.newrelic.agent.", + "network_signature": "js-agent\\.newrelic\\.com|mobile-collector\\.newrelic\\.com|newrelic\\.com|nr-data\\.net", + "website": "http://www.newrelic.com" + }, + "131": { + "id": 131, + "name": "AppAnalytics", + "creation_date": "2018-03-04", + "code_signature": "io.appanalytics.sdk", + "network_signature": "", + "website": "http://appanalytics.io/" + }, + "132": { + "id": 132, + "name": "Applause", + "creation_date": "2018-03-04", + "code_signature": "com.applause.android.", + "network_signature": "", + "website": "http://www.applause.com" + }, + "133": { + "id": 133, + "name": "Quantcast", + "creation_date": "2018-03-04", + "code_signature": "com.quantcast.measurement.service.", + "network_signature": "quantcast\\.com|quantcast\\.net", + "website": "http://www.quantcast.com" + }, + "135": { + "id": 135, + "name": "Apptimize", + "creation_date": "2018-03-04", + "code_signature": "com.apptimize.", + "network_signature": "brahe\\.apptimize\\.com|md-a-c\\.apptimize\\.com|md-a-s\\.apptimize\\.com", + "website": "http://www.apptimize.com" + }, + "136": { + "id": 136, + "name": "AppBrain", + "creation_date": "2018-03-04", + "code_signature": "com.appbrain.", + "network_signature": "sdk\\.appbrain\\.com", + "website": "https://www.appbrain.com/info/help/sdk/index.html" + }, + "137": { + "id": 137, + "name": "Dynatrace", + "creation_date": "2018-03-04", + "code_signature": "com.dynatrace.android.app|com.dynatrace.agent|com.dynatrace.tools", + "network_signature": "\\.dynatrace\\.com", + "website": "https://www.dynatrace.com" + }, + "138": { + "id": 138, + "name": "Matomo (Piwik)", + "creation_date": "2018-03-04", + "code_signature": "org.piwik|org.piwik.mobile|org.matomo", + "network_signature": "matomo\\.org", + "website": "https://matomo.org/mobile" + }, + "140": { + "id": 140, + "name": "AppMetrica", + "creation_date": "2018-03-04", + "code_signature": "com.yandex.metrica.", + "network_signature": "analytics\\.mobile\\.yandex\\.net|appmetrica\\.yandex\\.com|banners-slb\\.mobile\\.yandex\\.net|banners\\.mobile\\.yandex\\.net|mc\\.yandex\\.ru|report\\.appmetrica\\.yandex\\.net|startup\\.mobile\\.yandex\\.net", + "website": "https://tech.yandex.com/metrica-mobile-sdk/" + }, + "142": { + "id": 142, + "name": "Singlespot", + "creation_date": "2018-08-16", + "code_signature": "com.sptproximitykit.", + "network_signature": "singlespot\\.com", + "website": "https://www.singlespot.com/" + }, + "143": { + "id": 143, + "name": "Sensoro", + "creation_date": "2018-08-16", + "code_signature": "com.sensoro.beacon.kit.|com.sensoro.cloud", + "network_signature": "", + "website": "https://www.sensoro.com/" + }, + "144": { + "id": 144, + "name": "Sense360", + "creation_date": "2018-08-16", + "code_signature": "com.sense360.android.quinoa.lib.Sense360", + "network_signature": "android-quinoa-config-prod\\.sense360eng\\.com|incoming-data-sense360\\.s3\\.amazonaws\\.com|quinoa-personal-identify-prod\\.sense360eng\\.com", + "website": "https://sense360.com/" + }, + "145": { + "id": 145, + "name": "Rubicon Project", + "creation_date": "2018-08-16", + "code_signature": "com.rfm.sdk", + "network_signature": "ads\\.rubiconproject\\.com|fastlane\\.rubiconproject\\.com|optimized-by\\.rubiconproject\\.com|pixel\\.rubiconproject\\.com|stats\\.aws\\.rubiconproject\\.com|tap2-cdn\\.rubiconproject\\.com|video-ads\\.rubiconproject\\.com", + "website": "https://rubiconproject.com" + }, + "146": { + "id": 146, + "name": "ironSource", + "creation_date": "2018-08-16", + "code_signature": "com.ironsource.", + "network_signature": "", + "website": "https://www.ironsrc.com" + }, + "147": { + "id": 147, + "name": "Heyzap (bought by Fyber)", + "creation_date": "2018-08-16", + "code_signature": "com.heyzap.sdk.ads.", + "network_signature": "ads\\.heyzap\\.com|fyc\\.heyzap\\.com|img-cloudflare-2\\.haizap\\.com|img-cloudflare\\.haizap\\.com|med\\.heyzap\\.com|x\\.heyzap\\.com", + "website": "https://www.heyzap.com" + }, + "148": { + "id": 148, + "name": "Gigya", + "creation_date": "2018-08-16", + "code_signature": "com.gigya.", + "network_signature": "cdn1\\.gigya\\.com|cdn2\\.gigya\\.com|cdn3\\.gigya\\.com|cdn\\.gigya\\.com|cdns\\.us1\\.gigya\\.com", + "website": "https://www.gigya.com" + }, + "149": { + "id": 149, + "name": "Foresee", + "creation_date": "2018-08-16", + "code_signature": "com.foresee.sdk.ForeSee", + "network_signature": "4seeresults\\.com|analytics\\.foresee\\.com|foresee\\.com|foreseeresults\\.com|i\\.4see\\.mobi|rec\\.replay\\.answerscloud\\.com", + "website": "https://www.foresee.com" + }, + "150": { + "id": 150, + "name": "Fiksu", + "creation_date": "2018-08-16", + "code_signature": "com.fiksu.asotracking", + "network_signature": "a\\.fiksu\\.com|sdk\\.fiksu\\.com", + "website": "https://fiksu.com" + }, + "151": { + "id": 151, + "name": "Ensighten", + "creation_date": "2018-08-16", + "code_signature": "com.ensighten.", + "network_signature": "nexus\\.ensighten\\.com", + "website": "https://www.ensighten.com" + }, + "152": { + "id": 152, + "name": "Dynamic Yield", + "creation_date": "2018-08-16", + "code_signature": "com.dynamicyield.", + "network_signature": "adm\\.dynamicyield\\.com|api\\.dynamicyield\\.com|cdn\\.dynamicyield\\.com|px\\.dynamicyield\\.com|st\\.dynamicyield\\.com", + "website": "https://www.dynamicyield.com" + }, + "153": { + "id": 153, + "name": "BlueKai (acquired by Oracle)", + "creation_date": "2018-08-16", + "code_signature": "com.bluekai.sdk.", + "network_signature": "stags\\.bluekai\\.com|tags\\.bluekai\\.com", + "website": "http://bluekai.com/registry/" + }, + "154": { + "id": 154, + "name": "BlueConic", + "creation_date": "2018-08-16", + "code_signature": "com.blueconic", + "network_signature": "", + "website": "https://www.blueconic.com" + }, + "155": { + "id": 155, + "name": "Apteligent by VMWare (formerly Crittercism)", + "creation_date": "2018-08-16", + "code_signature": "com.crittercism.app.Crittercism", + "network_signature": "api\\.crittercism\\.com|appload\\.ingest\\.crittercism\\.com|txn\\.ingest\\.crittercism\\.com", + "website": "http://www.apteligent.com" + }, + "156": { + "id": 156, + "name": "AdFit (Daum)", + "creation_date": "2018-08-16", + "code_signature": "com.kakao.adfit.ads.", + "network_signature": "analytics\\.ad\\.daum\\.net|statistics\\.videofarm\\.daum\\.net", + "website": "https://www.daum.net" + }, + "157": { + "id": 157, + "name": "Adform", + "creation_date": "2018-08-16", + "code_signature": "com.adform.sdk.", + "network_signature": "adform\\.com|adformdsp\\.net|adx\\.adform\\.net|files\\.adform\\.net|track\\.adform\\.net", + "website": "https://site.adform.com" + }, + "158": { + "id": 158, + "name": "Adfurikun", + "creation_date": "2018-08-16", + "code_signature": "jp.tjkapp.adfurikunsdk.", + "network_signature": "adfurikun\\.jp|ginf\\.adfurikun\\.jp", + "website": "https://adfurikun.jp/adfurikun/" + }, + "159": { + "id": 159, + "name": "Mobvista", + "creation_date": "2019-03-10", + "code_signature": "com.mobvista.", + "network_signature": "mobvista\\.com", + "website": "https://www.mobvista.com/" + }, + "160": { + "id": 160, + "name": "Placed", + "creation_date": "2019-03-10", + "code_signature": "com.placed.client", + "network_signature": "", + "website": "http://placed.com/" + }, + "161": { + "id": 161, + "name": "Adot", + "creation_date": "2019-03-10", + "code_signature": "com.adotmob", + "network_signature": "sdk\\.adotmob\\.com|sync\\.adotmob\\.com|tracker\\.adotmob\\.com", + "website": "https://we-are-adot.com/" + }, + "162": { + "id": 162, + "name": "Appodeal", + "creation_date": "2019-03-10", + "code_signature": "com.appodeal.ads.", + "network_signature": "appodeal\\.com|appodealx\\.com", + "website": "https://www.appodeal.com" + }, + "163": { + "id": 163, + "name": "AppMonet", + "creation_date": "2019-03-10", + "code_signature": "com.monet.", + "network_signature": "", + "website": "http://appmonet.com" + }, + "164": { + "id": 164, + "name": "Soomla", + "creation_date": "2019-03-10", + "code_signature": "com.soomla.", + "network_signature": "soom\\.la", + "website": "https://soomla.com/" + }, + "165": { + "id": 165, + "name": "Adincube", + "creation_date": "2019-03-10", + "code_signature": "com.adincube.sdk.", + "network_signature": "sdk\\.adincube\\.com", + "website": "https://www.adincube.com/" + }, + "166": { + "id": 166, + "name": "Persona.ly", + "creation_date": "2019-03-10", + "code_signature": "ly.persona.sdk", + "network_signature": "dev-api\\.persona\\.ly|dev\\.dsp\\.persona\\.ly|dev\\.persona\\.ly|dsp\\.persona\\.ly|persona\\.ly|rtb\\.persona\\.ly|sdk\\.persona\\.ly", + "website": "http://persona.ly/" + }, + "167": { + "id": 167, + "name": "Branch", + "creation_date": "2019-03-10", + "code_signature": "io.branch.", + "network_signature": "api\\.branch\\.io", + "website": "https://branch.io/" + }, + "168": { + "id": 168, + "name": "Cheetah Ads", + "creation_date": "2019-03-10", + "code_signature": "com.cmcm.", + "network_signature": "cmcm\\.com", + "website": "https://www.cmcm.com/" + }, + "169": { + "id": 169, + "name": "Vungle", + "creation_date": "2019-03-10", + "code_signature": "com.vungle.publisher.", + "network_signature": "ads\\.api\\.vungle\\.com|akamai\\.vungle\\-cdn\\.vungle\\.com|api\\.vungle\\.akadns\\.net|api\\.vungle\\.com|bd\\.vungle\\.com|billboard\\.vungle\\.com|cdn\\-lb\\.vungle\\.com|ci\\.vungle\\.com|data\\.vungle\\.com|ingest\\.vungle\\.com|jaeger\\.vungle\\.com|ltv\\-data\\-api\\.kube\\-prod\\.vungle\\.com|monitoring\\.vungle\\.com|ssl\\.vungle\\.com|v\\.vungle\\.com", + "website": "https://vungle.com" + }, + "170": { + "id": 170, + "name": "Criteo", + "creation_date": "2019-03-10", + "code_signature": "com.criteo.", + "network_signature": "criteo\\.com", + "website": "https://www.criteo.com/" + }, + "171": { + "id": 171, + "name": "Mapbox", + "creation_date": "2019-03-10", + "code_signature": "com.mapbox.mapboxsdk.", + "network_signature": "a\\.tiles\\.mapbox\\.com|api\\.tiles\\.mapbox\\.com", + "website": "https://www.mapbox.com/" + }, + "172": { + "id": 172, + "name": "Optimizely", + "creation_date": "2019-03-10", + "code_signature": "com.optimizely.", + "network_signature": "optimizely\\.com", + "website": "https://www.optimizely.com/" + }, + "173": { + "id": 173, + "name": "Taboola", + "creation_date": "2019-03-10", + "code_signature": "com.taboola.", + "network_signature": "taboola\\.com", + "website": "https://www.taboola.com/" + }, + "174": { + "id": 174, + "name": "CleverTap", + "creation_date": "2019-03-10", + "code_signature": "com.clevertap.", + "network_signature": "wzrkt\\.com", + "website": "https://clevertap.com/" + }, + "175": { + "id": 175, + "name": "myTracker", + "creation_date": "2019-03-10", + "code_signature": "com.my.tracker.", + "network_signature": "tracker-api\\.my\\.com", + "website": "https://tracker.my.com/" + }, + "176": { + "id": 176, + "name": "Cloudmobi", + "creation_date": "2019-03-10", + "code_signature": "com.cloudtech.", + "network_signature": "api\\.cloudmobi\\.net|cloudmobi\\.net|logger\\.cloudmobi\\.net|vast\\.cloudmobi\\.net", + "website": "http://www.cloudmobi.net/" + }, + "177": { + "id": 177, + "name": "ADLIB", + "creation_date": "2019-03-10", + "code_signature": "com.mocoplex.adlib.", + "network_signature": "adlibr\\.com", + "website": "https://adlibr.com" + }, + "178": { + "id": 178, + "name": "Brightcove", + "creation_date": "2019-03-19", + "code_signature": "com.brightcove", + "network_signature": "metrics\\.brightcove\\.com", + "website": "https://www.brightcove.com" + }, + "179": { + "id": 179, + "name": "DOV-E", + "creation_date": "2019-03-19", + "code_signature": "com.dv.", + "network_signature": "\\.dov-e\\.com", + "website": "https://www.dov-e.com/" + }, + "180": { + "id": 180, + "name": "InMarket", + "creation_date": "2019-03-19", + "code_signature": "com.inmarket", + "network_signature": "m2m-api\\.inmarket\\.com", + "website": "https://inmarket.com/" + }, + "181": { + "id": 181, + "name": "Pilgrim by Foursquare", + "creation_date": "2019-03-19", + "code_signature": "com.foursquare.pilgrim|com.foursquare.pilgrimsdk.android", + "network_signature": "sdk\\.foursquare\\.com", + "website": "https://enterprise.foursquare.com/products/pilgrim" + }, + "182": { + "id": 182, + "name": "OtherLevels", + "creation_date": "2019-03-19", + "code_signature": "com.otherlevels.", + "network_signature": "api\\.otherlevels\\.com|geodata\\.otherlevels\\.com|mdn\\.otherlevels\\.com|rich\\.otherlevels\\.com|tags\\.otherlevels\\.com|ws\\.otherlevels\\.com", + "website": "https://www.otherlevels.com/" + }, + "183": { + "id": 183, + "name": "PubNative", + "creation_date": "2019-03-19", + "code_signature": "net.pubnative", + "network_signature": "pubnative\\.net", + "website": "https://pubnative.net/" + }, + "184": { + "id": 184, + "name": "Appnext", + "creation_date": "2019-03-19", + "code_signature": "com.appnext.", + "network_signature": "appnext.com", + "website": "https://www.appnext.com/" + }, + "185": { + "id": 185, + "name": "MobFox", + "creation_date": "2019-03-19", + "code_signature": "com.mobfox.", + "network_signature": "", + "website": "https://www.mobfox.com/" + }, + "186": { + "id": 186, + "name": "ShallWeAD", + "creation_date": "2019-03-19", + "code_signature": "com.jm.co.shallwead.sdk.|com.co.shallwead.sdk.", + "network_signature": "", + "website": "http://www.shallwead.com" + }, + "187": { + "id": 187, + "name": "deltaDNA", + "creation_date": "2019-03-19", + "code_signature": "com.deltadna", + "network_signature": "deltadna\\.net", + "website": "https://deltadna.com/" + }, + "188": { + "id": 188, + "name": "Display", + "creation_date": "2019-03-19", + "code_signature": "io.display.", + "network_signature": "display.io", + "website": "https://www.display.io/en/" + }, + "189": { + "id": 189, + "name": "HyprMX", + "creation_date": "2019-04-15", + "code_signature": "com.hyprmx.android.sdk.", + "network_signature": "hyprmx\\.com", + "website": "https://www.hyprmx.com" + }, + "190": { + "id": 190, + "name": "Bugly", + "creation_date": "2019-04-15", + "code_signature": "com.tencent.bugly.", + "network_signature": "bugly\\.qq\\.com", + "website": "https://bugly.qq.com/v2/" + }, + "191": { + "id": 191, + "name": "Duapps", + "creation_date": "2019-04-15", + "code_signature": "com.duapps.", + "network_signature": "duapps\\.com", + "website": "http://ad.duapps.com/" + }, + "192": { + "id": 192, + "name": "Swrve", + "creation_date": "2019-04-15", + "code_signature": "com.swrve.sdk", + "network_signature": "api\\.swrve\\.com|content\\.swrve\\.com", + "website": "https://www.swrve.com/" + }, + "193": { + "id": 193, + "name": "OneSignal", + "creation_date": "2019-04-15", + "code_signature": "com.onesignal.", + "network_signature": "onesignal\\.com", + "website": "https://onesignal.com/" + }, + "194": { + "id": 194, + "name": "Appdynamics", + "creation_date": "2019-04-15", + "code_signature": "com.appdynamics.", + "network_signature": "", + "website": "https://www.appdynamics.com/" + }, + "195": { + "id": 195, + "name": "Startapp", + "creation_date": "2019-04-15", + "code_signature": "com.startapp.android.publish", + "network_signature": "c2i\\.startappnetwork\\.com|c2s\\.startappnetwork\\.com|click\\.startappservice\\.com|dts\\.startappservice\\.com|events\\.startappservice\\.com|images\\.startappservice\\.com|imp\\.startappservice\\.com|info\\.static\\.startappservice\\.com|init\\.startappservice\\.com|req\\.startappservice\\.com|soda\\.startappservice\\.com|startappservice\\.com|va\\.origin\\.startappservice\\.com", + "website": "https://www.startapp.com" + }, + "196": { + "id": 196, + "name": "AerServ", + "creation_date": "2019-04-15", + "code_signature": "com.aerserv.sdk.", + "network_signature": "ads\\.aerserv\\.com|debug\\.aerserv\\.com", + "website": "https://www.aerserv.com/" + }, + "197": { + "id": 197, + "name": "INFOnline", + "creation_date": "2019-04-15", + "code_signature": "de.infonline.", + "network_signature": "de\\.ioam\\.de", + "website": "https://www.infonline.de" + }, + "198": { + "id": 198, + "name": "myTarget", + "creation_date": "2019-04-15", + "code_signature": "com.my.target.", + "network_signature": ".target\\.my\\.com", + "website": "https://target.my.com/" + }, + "199": { + "id": 199, + "name": "Tapjoy", + "creation_date": "2019-04-15", + "code_signature": "com.tapjoy.", + "network_signature": "tapjoy\\.com|tapjoyads\\.com|www\\.5rocks\\.io", + "website": "https://www.tapjoy.com/" + }, + "200": { + "id": 200, + "name": "Mintegral", + "creation_date": "2019-04-15", + "code_signature": "com.mintegral.", + "network_signature": "analytics\\.rayjump\\.com|cdn-adn\\.rayjump\\.com|de01\\.rayjump\\.com|de\\.rayjump\\.com|detect\\.rayjump\\.com|fk-mtrack\\.rayjump\\.com|hybird\\.rayjump\\.com|jssdk\\.rayjump\\.com|net\\.rayjump\\.com|online\\.rayjump\\.com|rayjump\\.com|setting\\.rayjump\\.com|sg-mtrack\\.rayjump\\.com|sg01\\.rayjump\\.com|sg\\.rayjump\\.com|tknet\\.rayjump\\.com|us01\\.rayjump\\.com", + "website": "https://www.mintegral.com/en/" + }, + "201": { + "id": 201, + "name": "Gimbal", + "creation_date": "2019-04-15", + "code_signature": "com.gimbal.android", + "network_signature": "analytics-server\\.gimbal\\.com|api\\.gimbal\\.com|registration\\.gimbal\\.com|sdk-info\\.gimbal\\.com", + "website": "https://gimbal.com/" + }, + "202": { + "id": 202, + "name": "Conviva", + "creation_date": "2019-04-15", + "code_signature": "com.conviva.", + "network_signature": "cws\\.conviva\\.com", + "website": "https://www.conviva.com/" + }, + "203": { + "id": 203, + "name": "Auditude", + "creation_date": "2019-05-02", + "code_signature": "com.auditude.ads", + "network_signature": "auditude\\.com", + "website": "https://www.adobe.com/privacy/policies/auditude.html" + }, + "204": { + "id": 204, + "name": "Instreamatic (Adman)", + "creation_date": "2019-05-02", + "code_signature": "com.instreamatic", + "network_signature": "instreamatic\\.com", + "website": "http://instreamatic.com/" + }, + "205": { + "id": 205, + "name": "GameAnalytics", + "creation_date": "2019-06-12", + "code_signature": "com.gameanalytics.sdk", + "network_signature": "", + "website": "https://gameanalytics.com/features" + }, + "206": { + "id": 206, + "name": "Instabug", + "creation_date": "2019-06-12", + "code_signature": "com.instabug.library.tracking|com.instabug.bug", + "network_signature": "", + "website": "https://instabug.com/crash-reporting" + }, + "207": { + "id": 207, + "name": "Bugsnag", + "creation_date": "2019-06-12", + "code_signature": "com.bugsnag.", + "network_signature": "", + "website": "https://www.bugsnag.com/" + }, + "208": { + "id": 208, + "name": "Moodmedia", + "creation_date": "2019-06-20", + "code_signature": "com.moodmedia", + "network_signature": "moodpresence\\.com", + "website": "https://us.moodmedia.com/" + }, + "209": { + "id": 209, + "name": "Houndify", + "creation_date": "2019-06-20", + "code_signature": "com.hound", + "network_signature": "houndify\\.com", + "website": "https://www.houndify.com/" + }, + "210": { + "id": 210, + "name": "OpenX", + "creation_date": "2019-06-20", + "code_signature": "com.openx.view.plugplay|com.openx.android_sdk_openx", + "network_signature": "openx\\.com|openx\\.net|openx\\.org|us-ads\\.openx\\.net", + "website": "https://www.openx.com/" + }, + "211": { + "id": 211, + "name": "Taplytics", + "creation_date": "2019-06-20", + "code_signature": "com.taplytics.sdk", + "network_signature": "api\\.taplytics\\.com|ping\\.tapylitics\\.com", + "website": "https://taplytics.com" + }, + "212": { + "id": 212, + "name": "Yinzcam Sobek", + "creation_date": "2019-06-20", + "code_signature": "com.yinzcam.sobek", + "network_signature": "analytics\\.yinzcam\\.com", + "website": "http://www.yinzcam.com/" + }, + "213": { + "id": 213, + "name": "Ooyala", + "creation_date": "2019-07-01", + "code_signature": "com.ooyala", + "network_signature": "ooyala\\.com", + "website": "https://www.ooyala.com/" + }, + "214": { + "id": 214, + "name": "Kiip", + "creation_date": "2019-08-20", + "code_signature": "me.kiip.sdk", + "network_signature": "kiip\\.me", + "website": "https://www.ninthdecimal.com/" + }, + "215": { + "id": 215, + "name": "MobPower", + "creation_date": "2019-08-20", + "code_signature": "com.mobpower.", + "network_signature": "api\\.mobpowertech\\.com|log\\.mobpowertech\\.com|mobpowertech\\.com|scheme\\.mobpowertech\\.com", + "website": "https://home.mobpowertech.com/" + }, + "216": { + "id": 216, + "name": "AdBuddiz", + "creation_date": "2019-08-20", + "code_signature": "com.purplebrain.adbuddiz.sdk.", + "network_signature": "sdk\\.adbuddiz\\.com", + "website": "http://www.adbuddiz.com/abuse?hl=fr" + }, + "218": { + "id": 218, + "name": "Integral Ad Science", + "creation_date": "2019-10-26", + "code_signature": "com.integralads.avid.library", + "network_signature": "adsafeprotected\\.com|iasds01\\.com|integralads\\.com", + "website": "https://integralads.com" + }, + "219": { + "id": 219, + "name": "AltBeacon", + "creation_date": "2019-10-26", + "code_signature": "org.altbeacon.beacon.|com.altbeacon.beacon.", + "network_signature": "", + "website": "https://altbeacon.org" + }, + "220": { + "id": 220, + "name": "Salesforce Marketing Cloud", + "creation_date": "2019-10-26", + "code_signature": "com.salesforce.marketingcloud", + "network_signature": "", + "website": "https://www.salesforce.com/products/marketing-cloud/" + }, + "221": { + "id": 221, + "name": "Mozilla Telemetry", + "creation_date": "2019-10-26", + "code_signature": "org.mozilla.telemetry|org.mozilla.gecko.telemetry", + "network_signature": "", + "website": "https://wiki.mozilla.org/Telemetry" + }, + "222": { + "id": 222, + "name": "nend", + "creation_date": "2019-10-26", + "code_signature": "net.nend.android", + "network_signature": "", + "website": "https://nend.net/en/" + }, + "223": { + "id": 223, + "name": "Pusher", + "creation_date": "2019-10-26", + "code_signature": "com.pusher.client.", + "network_signature": "", + "website": "https://pusher.com/" + }, + "224": { + "id": 224, + "name": "FreeWheel", + "creation_date": "2019-10-26", + "code_signature": "tv.freewheel.ad.", + "network_signature": "fwmrm\\.net", + "website": "http://freewheel.tv/" + }, + "225": { + "id": 225, + "name": "TNK Factory", + "creation_date": "2019-10-26", + "code_signature": "com.tnkfactory.ad", + "network_signature": "", + "website": "http://www.tnkfactory.com" + }, + "226": { + "id": 226, + "name": "Axonix", + "creation_date": "2019-10-26", + "code_signature": "com.axonix.android.sdk|com.mobclix.android.sdk", + "network_signature": "ads\\.mobclix\\.com|axonix\\.com|data\\.mobclix\\.com|mobclix\\.com|s\\.mobclix\\.com", + "website": "http://axonix.com/" + }, + "227": { + "id": 227, + "name": "Gemius HeatMap", + "creation_date": "2019-10-26", + "code_signature": "com.gemius.sdk", + "network_signature": "gemius\\.pl", + "website": "https://heatmap.gemius.com" + }, + "228": { + "id": 228, + "name": "YouAppi", + "creation_date": "2019-10-26", + "code_signature": "com.youappi.sdk.", + "network_signature": "", + "website": "https://www.youappi.com" + }, + "229": { + "id": 229, + "name": "Adobe Experience Cloud", + "creation_date": "2019-10-26", + "code_signature": "com.adobe.marketing.mobile", + "network_signature": "", + "website": "https://www.adobe.com/experience-cloud.html" + }, + "230": { + "id": 230, + "name": "Teads", + "creation_date": "2019-10-26", + "code_signature": "tv.teads.", + "network_signature": "teads\\.tv", + "website": "https://www.teads.tv" + }, + "231": { + "id": 231, + "name": "In Loco", + "creation_date": "2019-10-26", + "code_signature": "com.inlocomedia.android", + "network_signature": "inlocomedia\\.com", + "website": "https://inloco.com.br" + }, + "232": { + "id": 232, + "name": "IQzone", + "creation_date": "2019-10-26", + "code_signature": "com.iqzone", + "network_signature": "", + "website": "https://iqzone.com" + }, + "233": { + "id": 233, + "name": "Bugfender", + "creation_date": "2019-10-26", + "code_signature": "com.bugfender.sdk.", + "network_signature": "", + "website": "https://bugfender.com/" + }, + "234": { + "id": 234, + "name": "Wootric", + "creation_date": "2019-10-26", + "code_signature": "com.wootric.androidsdk.", + "network_signature": ".wootric\\.com\\.herokudns\\.com|wootric\\.com", + "website": "http://wootric.com" + }, + "235": { + "id": 235, + "name": "KIDOZ", + "creation_date": "2019-10-26", + "code_signature": "com.kidoz.sdk", + "network_signature": "", + "website": "https://kidoz.net/kidoz-sdk/" + }, + "236": { + "id": 236, + "name": "PubMatic", + "creation_date": "2019-10-26", + "code_signature": "com.pubmatic.sdk", + "network_signature": "ads\\.pubmatic\\.com|aktrack\\.pubmatic\\.com|gads\\.pubmatic\\.com|image2\\.pubmatic\\.com|simage2\\.pubmatic\\.com", + "website": "https://pubmatic.com/" + }, + "237": { + "id": 237, + "name": "Kissmetrics", + "creation_date": "2019-11-09", + "code_signature": "com.kissmetrics", + "network_signature": "", + "website": "https://www.kissmetricshq.com/" + }, + "238": { + "id": 238, + "name": "Microsoft Visual Studio App Center Crashes", + "creation_date": "2019-11-09", + "code_signature": "com.microsoft.appcenter.crashes", + "network_signature": "", + "website": "https://appcenter.ms/" + }, + "239": { + "id": 239, + "name": "Webtrekk", + "creation_date": "2019-11-09", + "code_signature": "com.webtrekk.webtrekksdk", + "network_signature": "", + "website": "https://www.webtrekk.com/" + }, + "240": { + "id": 240, + "name": "Google Analytics Plugin (Cordova)", + "creation_date": "2019-11-09", + "code_signature": "com.danielcwilson.plugins.analytics", + "network_signature": "", + "website": "https://analytics.withgoogle.com/" + }, + "241": { + "id": 241, + "name": "Bugsee", + "creation_date": "2019-11-09", + "code_signature": "com.bugsee.library.Bugsee", + "network_signature": "", + "website": "https://www.bugsee.com/" + }, + "242": { + "id": 242, + "name": "Splunk MINT", + "creation_date": "2019-11-09", + "code_signature": "com.splunk.mint", + "network_signature": "", + "website": "https://mint.splunk.com/" + }, + "243": { + "id": 243, + "name": "Microsoft Visual Studio App Center Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.microsoft.appcenter.analytics|com.microsoft.azure.mobile.analytics", + "network_signature": "", + "website": "https://appcenter.ms/" + }, + "244": { + "id": 244, + "name": "Nielsen", + "creation_date": "2019-11-09", + "code_signature": "com.nielsen.app", + "network_signature": "", + "website": "https://www.nielsen.com" + }, + "245": { + "id": 245, + "name": "Reveal Mobile", + "creation_date": "2019-11-09", + "code_signature": "com.stepleaderdigital.reveal", + "network_signature": "", + "website": "https://revealmobile.com/" + }, + "246": { + "id": 246, + "name": "Metrics", + "creation_date": "2019-11-09", + "code_signature": "com.codahale.metrics", + "network_signature": "", + "website": "https://metrics.dropwizard.io" + }, + "247": { + "id": 247, + "name": "Repro", + "creation_date": "2019-11-09", + "code_signature": "io.repro.android.Repro", + "network_signature": "", + "website": "https://repro.io/" + }, + "248": { + "id": 248, + "name": "Sensors Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.sensorsdata.analytics.android.sdk", + "network_signature": "", + "website": "https://www.sensorsdata.cn" + }, + "249": { + "id": 249, + "name": "Tenjin", + "creation_date": "2019-11-09", + "code_signature": "com.tenjin.android.TenjinSDK", + "network_signature": "", + "website": "https://www.tenjin.com/" + }, + "250": { + "id": 250, + "name": "Tapstream", + "creation_date": "2019-11-09", + "code_signature": "com.tapstream.sdk", + "network_signature": "", + "website": "https://www.tapstream.com/" + }, + "251": { + "id": 251, + "name": "Singular", + "creation_date": "2019-11-09", + "code_signature": "com.singular.sdk", + "network_signature": "", + "website": "https://singular.net/" + }, + "252": { + "id": 252, + "name": "CallDorado", + "creation_date": "2019-11-09", + "code_signature": "com.calldorado.android", + "network_signature": "", + "website": "http://calldorado.com" + }, + "253": { + "id": 253, + "name": "UXCam", + "creation_date": "2019-11-09", + "code_signature": "com.uxcam.UXCam", + "network_signature": "", + "website": "https://uxcam.com/" + }, + "254": { + "id": 254, + "name": "Upsight", + "creation_date": "2019-11-09", + "code_signature": "com.upsight.android", + "network_signature": "", + "website": "https://www.upsight.com/" + }, + "255": { + "id": 255, + "name": "Appcelerator Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.appcelerator.aps.|org.appcelerator.titanium.analytics", + "network_signature": "appcelerator\\.com|appcelerator\\.net", + "website": "https://www.appcelerator.com" + }, + "256": { + "id": 256, + "name": "Adbrix", + "creation_date": "2019-11-09", + "code_signature": "com.igaworks.adbrix", + "network_signature": "ad-brix\\.com", + "website": "http://ad-brix.com/" + }, + "257": { + "id": 257, + "name": "Cauly", + "creation_date": "2019-11-09", + "code_signature": "com.fsn.cauly|com.trid.tridad", + "network_signature": "ad\\.cauly\\.co\\.kr", + "website": "https://www.cauly.net" + }, + "258": { + "id": 258, + "name": "Tapdaq", + "creation_date": "2019-11-09", + "code_signature": "com.tapdaq.sdk.", + "network_signature": "ads\\.tapdaq\\.com", + "website": "https://www.tapdaq.com/" + }, + "259": { + "id": 259, + "name": "Verve", + "creation_date": "2019-11-09", + "code_signature": "com.vervewireless.advert.", + "network_signature": "", + "website": "https://www.verve.com" + }, + "260": { + "id": 260, + "name": "Apsalar", + "creation_date": "2019-11-09", + "code_signature": "com.apsalar.sdk.", + "network_signature": "e-ssl\\.apsalar\\.com|e\\.apsalar\\.com", + "website": "https://singular.net" + }, + "261": { + "id": 261, + "name": "PingStart", + "creation_date": "2019-11-09", + "code_signature": "com.pingstart.adsdk.", + "network_signature": "api\\.pingstart\\.com", + "website": "http://pingstart.com" + }, + "262": { + "id": 262, + "name": "Keen", + "creation_date": "2019-11-09", + "code_signature": "io.keen.client.", + "network_signature": "api\\.keen\\.io", + "website": "https://keen.io" + }, + "263": { + "id": 263, + "name": "Revmob", + "creation_date": "2020-01-02", + "code_signature": "com.revmob.ads.", + "network_signature": "", + "website": "https://www.crunchbase.com/organization/revmob" + }, + "264": { + "id": 264, + "name": "Emarsys Predict", + "creation_date": "2020-01-02", + "code_signature": "com.emarsys.predict", + "network_signature": "recommender\\.scarabresearch\\.com", + "website": "https://help.emarsys.com/hc/categories/115000670425-Predict" + }, + "265": { + "id": 265, + "name": "Lotame", + "creation_date": "2020-01-02", + "code_signature": "com.lotame.android", + "network_signature": "ad\\.crwdcntrl\\.net", + "website": "https://www.lotame.com/" + }, + "266": { + "id": 266, + "name": "FollowAnalytics", + "creation_date": "2020-01-02", + "code_signature": "com.followanalytics.", + "network_signature": "sdk\\.follow-apps\\.com", + "website": "https://www.followanalytics.com" + }, + "267": { + "id": 267, + "name": "Chartbeat", + "creation_date": "2020-01-02", + "code_signature": "com.chartbeat.androidsdk", + "network_signature": ".chartbeat\\.com|.chartbeat\\.net", + "website": "https://chartbeat.com/" + }, + "268": { + "id": 268, + "name": "MoEngage", + "creation_date": "2020-01-02", + "code_signature": "com.moengage.", + "network_signature": "apiv2\\.moengage\\.com", + "website": "https://www.moengage.com/" + }, + "269": { + "id": 269, + "name": "Altamob", + "creation_date": "2020-01-02", + "code_signature": "com.altamob.sdk", + "network_signature": "api\\.altamob\\.com", + "website": "https://www.altamob.com/en/index.html" + }, + "270": { + "id": 270, + "name": "Tealeaf", + "creation_date": "2020-02-25", + "code_signature": "com.tl.uic.Tealeaf", + "network_signature": "", + "website": "https://acoustic.co/products/experience-analytics/" + }, + "271": { + "id": 271, + "name": "AMoAd", + "creation_date": "2020-02-25", + "code_signature": "com.amoad.", + "network_signature": "", + "website": "https://www.amoad.com" + }, + "272": { + "id": 272, + "name": "AdAdapted", + "creation_date": "2020-02-25", + "code_signature": "com.adadapted.android.sdk.", + "network_signature": "ads\\.adadapted\\.com", + "website": "https://www.adadapted.com" + }, + "273": { + "id": 273, + "name": "AdMuing", + "creation_date": "2020-02-25", + "code_signature": "com.admuing.danmaku.", + "network_signature": "", + "website": "https://github.com/admuing" + }, + "274": { + "id": 274, + "name": "Adcash", + "creation_date": "2020-02-25", + "code_signature": "com.adcash.mobileads.", + "network_signature": "", + "website": "https://adcash.com" + }, + "275": { + "id": 275, + "name": "Admixer", + "creation_date": "2020-02-25", + "code_signature": "com.admixer|net.admixer.sdk", + "network_signature": "admixer\\.co\\.kr", + "website": "http://admixer.co.kr/" + }, + "276": { + "id": 276, + "name": "Admost", + "creation_date": "2020-02-25", + "code_signature": "admost.sdk.|admost.adserver.ads.", + "network_signature": "cdn\\-api\\.admost\\.com|go\\.admost\\.com|med\\-api\\.admost\\.com", + "website": "https://admost.com/" + }, + "277": { + "id": 277, + "name": "Alohalytics", + "creation_date": "2020-02-25", + "code_signature": "org.alohalytics.", + "network_signature": "", + "website": "https://github.com/biodranik/Alohalytics" + }, + "278": { + "id": 278, + "name": "Amobee", + "creation_date": "2020-02-25", + "code_signature": "com.amobee.", + "network_signature": "amobee\\.com", + "website": "https://amobee.com" + }, + "279": { + "id": 279, + "name": "Anagog", + "creation_date": "2020-02-25", + "code_signature": "com.anagog.jedai", + "network_signature": "", + "website": "https://anagog.com" + }, + "280": { + "id": 280, + "name": "Bazaarvoice", + "creation_date": "2020-02-25", + "code_signature": "com.bazaarvoice.bvandroidsdk", + "network_signature": "", + "website": "https://www.bazaarvoice.com/" + }, + "281": { + "id": 281, + "name": "BeaconsInSpace (Fysical)", + "creation_date": "2020-02-25", + "code_signature": "com.beaconsinspace.android.beacon.detector.", + "network_signature": "", + "website": "https://beaconsinspace.com" + }, + "282": { + "id": 282, + "name": "Conversant", + "creation_date": "2020-02-25", + "code_signature": "com.conversantmedia", + "network_signature": "conversantmedia\\.com", + "website": "https://www.conversantmedia.com" + }, + "283": { + "id": 283, + "name": "Glympse", + "creation_date": "2020-02-25", + "code_signature": "com.glympse.android.", + "network_signature": "\\.glympse\\.com", + "website": "https://glympse.com" + }, + "284": { + "id": 284, + "name": "Herow", + "creation_date": "2020-02-25", + "code_signature": "com.connecthings.herow", + "network_signature": "", + "website": "https://herow.io/" + }, + "285": { + "id": 285, + "name": "Placer", + "creation_date": "2020-02-25", + "code_signature": "com.placer.client.Placer", + "network_signature": "", + "website": "https://placer.io/" + }, + "286": { + "id": 286, + "name": "PushSpring", + "creation_date": "2020-02-25", + "code_signature": "com.pushspring.sdk.PushSpring", + "network_signature": "api\\.pushspring\\.com", + "website": "http://www.pushspring.com/" + }, + "287": { + "id": 287, + "name": "Pyze", + "creation_date": "2020-02-25", + "code_signature": "com.pyze.", + "network_signature": "pyze\\.com", + "website": "https://pyze.com/" + }, + "288": { + "id": 288, + "name": "Radar", + "creation_date": "2020-02-25", + "code_signature": "io.radar.sdk.Radar", + "network_signature": "", + "website": "https://radar.io/" + }, + "289": { + "id": 289, + "name": "Roximity", + "creation_date": "2020-02-25", + "code_signature": "com.roximity.sdk.", + "network_signature": "app\\.roximity\\.com", + "website": "http://roximity.com/" + }, + "290": { + "id": 290, + "name": "Sentiance", + "creation_date": "2020-02-25", + "code_signature": "com.sentiance.sdk.", + "network_signature": "api\\.sentiance\\.com", + "website": "https://www.sentiance.com/" + }, + "291": { + "id": 291, + "name": "SmartLook", + "creation_date": "2020-02-25", + "code_signature": "com.smartlook.sdk.smartlook.", + "network_signature": "smartlook\\.com", + "website": "https://www.smartlook.com/" + }, + "292": { + "id": 292, + "name": "Square Metrics", + "creation_date": "2020-02-25", + "code_signature": "com.beaconinside.proximitysdk.ProximityService", + "network_signature": "api\\.beaconinside\\.com", + "website": "https://www.squaremetrics.com" + }, + "293": { + "id": 293, + "name": "TalkingData", + "creation_date": "2020-02-25", + "code_signature": "com.talkingdata.sdk.|com.tendcloud.tenddata.|com.talkingdata.appanalytics.|com.talkingdata.adtracking.|com.tendcloud.appcpa.|com.apptalkingdata.push.|com.gametalkingdata.push.", + "network_signature": "account\\.talkingdata\\.com|av1\\.xdrig\\.com|cloud\\.xdrig\\.com|m\\.talkingdata\\.com|push\\.xdrig\\.com", + "website": "https://www.talkingdata.com/" + }, + "294": { + "id": 294, + "name": "flymob", + "creation_date": "2020-02-25", + "code_signature": "com.flymob.sdk.", + "network_signature": "", + "website": "https://flymob.com/" + }, + "295": { + "id": 295, + "name": "AdFalcon", + "creation_date": "2020-06-08", + "code_signature": "com.noqoush.adfalcon.android.sdk", + "network_signature": "", + "website": "http://adfalcon.com" + }, + "296": { + "id": 296, + "name": "Bitly", + "creation_date": "2020-06-08", + "code_signature": "com.bitly.Bitly", + "network_signature": "", + "website": "https://bitly.com/" + }, + "297": { + "id": 297, + "name": "Enhance", + "creation_date": "2020-06-08", + "code_signature": "co.enhance.Enhance", + "network_signature": "app-config\\.enhance\\.co|data-location\\.enhance\\.co", + "website": "https://enhance.co" + }, + "298": { + "id": 298, + "name": "Esri ArcGIS", + "creation_date": "2020-06-08", + "code_signature": "com.esri.arcgisruntime.", + "network_signature": "", + "website": "https://www.arcgis.com/" + }, + "299": { + "id": 299, + "name": "GIPHY Analytics", + "creation_date": "2020-06-08", + "code_signature": "com.giphy.sdk.analytics", + "network_signature": "api\\.giphy\\.com|pingback\\.giphy\\.com", + "website": "https://giphy.com/" + }, + "300": { + "id": 300, + "name": "Heap", + "creation_date": "2020-06-08", + "code_signature": "com.heapanalytics", + "network_signature": "heapanalytics.com", + "website": "https://heap.io/" + }, + "301": { + "id": 301, + "name": "Inneractive", + "creation_date": "2020-06-08", + "code_signature": "com.inneractive.api.ads", + "network_signature": "", + "website": "https://www.crunchbase.com/organization/inneractive" + }, + "302": { + "id": 302, + "name": "MDOTM", + "creation_date": "2020-06-08", + "code_signature": "com.mdotm.android", + "network_signature": "ads\\.mdotm\\.com", + "website": "http://mdotm.com/" + }, + "303": { + "id": 303, + "name": "Metaps", + "creation_date": "2020-06-08", + "code_signature": "com.metaps", + "network_signature": "", + "website": "http://www.metaps.com" + }, + "304": { + "id": 304, + "name": "Parse.ly", + "creation_date": "2020-06-08", + "code_signature": "com.parsely.parselyandroid", + "network_signature": "", + "website": "https://www.parse.ly/" + }, + "305": { + "id": 305, + "name": "Pollfish", + "creation_date": "2020-06-08", + "code_signature": "com.pollfish", + "network_signature": "", + "website": "https://www.pollfish.com" + }, + "306": { + "id": 306, + "name": "Qualtrics", + "creation_date": "2020-06-08", + "code_signature": "com.qualtrics.digital.", + "network_signature": "qualtrics\\.com", + "website": "http://www.qualtrics.com/" + }, + "307": { + "id": 307, + "name": "Tamoco", + "creation_date": "2020-06-08", + "code_signature": "com.tamoco.sdk", + "network_signature": "evt\\.tamoco\\.com", + "website": "https://www.tamoco.com/" + }, + "308": { + "id": 308, + "name": "Vpon", + "creation_date": "2020-06-08", + "code_signature": "com.vpon.ads", + "network_signature": "", + "website": "https://www.vpon.com/" + }, + "309": { + "id": 309, + "name": "YuMe", + "creation_date": "2020-06-08", + "code_signature": "com.yume.android", + "network_signature": "", + "website": "https://www.appbrain.com/stats/libraries/details/yume/yume" + }, + "310": { + "id": 310, + "name": "Zapr", + "creation_date": "2020-06-08", + "code_signature": "com.redbricklane.zapr", + "network_signature": ".zapr\\.in", + "website": "https://www.zapr.in/" + }, + "311": { + "id": 311, + "name": "mediba", + "creation_date": "2020-06-08", + "code_signature": "com.mediba.jp|mediba.ad.sdk.android.openx", + "network_signature": "", + "website": "https://www.mediba.jp" + }, + "312": { + "id": 312, + "name": "Google AdMob", + "creation_date": "2020-06-24", + "code_signature": "com.google.ads.|com.google.android.gms.ads.AdView|com.google.android.gms.ads.AdActivity|com.google.android.gms.ads.AdRequest|com.google.android.gms.ads.mediation|com.google.android.gms.ads.doubleclick", + "network_signature": "2mdn\\.net|\\.google\\.com|dmtry\\.com|doubleclick\\.com|doubleclick\\.net|mng-ads\\.com", + "website": "https://admob.google.com" + }, + "313": { + "id": 313, + "name": "Unacast Pure", + "creation_date": "2020-07-13", + "code_signature": "com.pure.internal.|com.pure.sdk.", + "network_signature": "", + "website": "https://www.unacast.com/" + }, + "314": { + "id": 314, + "name": "Factual", + "creation_date": "2020-09-19", + "code_signature": "com.factual.engine", + "network_signature": "api.factual.com", + "website": "https://www.factual.com" + }, + "315": { + "id": 315, + "name": "Footmarks", + "creation_date": "2020-09-19", + "code_signature": "com.footmarks.footmarkssdkm2", + "network_signature": "", + "website": "https://www.footmarks.com" + }, + "316": { + "id": 316, + "name": "OzTAM", + "creation_date": "2020-09-19", + "code_signature": "au.com.oztam.", + "network_signature": "deliver.oztam.com.au", + "website": "https://oztam.com.au/" + }, + "317": { + "id": 317, + "name": "Receptiv (formerly Mediabrix)", + "creation_date": "2020-09-19", + "code_signature": "com.mediabrix.android", + "network_signature": "", + "website": "https://www.receptiv.com/" + }, + "318": { + "id": 318, + "name": "Tutela", + "creation_date": "2020-09-19", + "code_signature": "com.tutelatechnologies.sdk", + "network_signature": "", + "website": "https://www.tutela.com/" + }, + "319": { + "id": 319, + "name": "Twine Data", + "creation_date": "2020-09-19", + "code_signature": "com.twine.sdk", + "network_signature": "", + "website": "https://www.truedata.co/" + }, + "320": { + "id": 320, + "name": "Verizon Ads", + "creation_date": "2020-09-19", + "code_signature": "com.verizon.ads", + "network_signature": "", + "website": "https://www.verizonmedia.com/" + }, + "321": { + "id": 321, + "name": "adPOPcorn", + "creation_date": "2020-09-19", + "code_signature": "com.igaworks.adpopcorn", + "network_signature": "", + "website": "https://adpopcorn.com/" + }, + "322": { + "id": 322, + "name": "maio by i-mobile", + "creation_date": "2020-09-19", + "code_signature": "jp.maio.sdk.android.", + "network_signature": "", + "website": "https://adpf-info.i-mobile.co.jp/en/" + }, + "323": { + "id": 323, + "name": "360Dialog", + "creation_date": "2020-09-19", + "code_signature": "com.threesixtydialog.sdk.", + "network_signature": "", + "website": "https://www.360dialog.com" + }, + "324": { + "id": 324, + "name": "ABTasty", + "creation_date": "2020-09-19", + "code_signature": "com.abtasty", + "network_signature": "abtasty\\.com", + "website": "https://www.abtasty.com" + }, + "325": { + "id": 325, + "name": "ACRCloud", + "creation_date": "2020-09-19", + "code_signature": "com.acrcloud", + "network_signature": "acrcloud.com|hb-minify-juc1ugur1qwqqqo4.stackpathdns.com", + "website": "https://acrcloud.com/" + }, + "326": { + "id": 326, + "name": "Aarki", + "creation_date": "2020-09-19", + "code_signature": "com.aarki", + "network_signature": "", + "website": "https://www.aarki.com" + }, + "327": { + "id": 327, + "name": "Actv8me", + "creation_date": "2020-09-19", + "code_signature": "me.actv8", + "network_signature": "actv8technologies\\.com", + "website": "https://www.actv8me.com/" + }, + "328": { + "id": 328, + "name": "IAB Open Measurement", + "creation_date": "2020-09-19", + "code_signature": "com.iab.omid.library", + "network_signature": "", + "website": "https://iabtechlab.com/" + }, + "333": { + "id": 333, + "name": "Huawei Mobile Services (HMS) Core", + "creation_date": "2020-11-25", + "code_signature": "com.huawei.hms.analytics|com.huawei.hms.location|com.huawei.hms.plugin.analytics", + "network_signature": "", + "website": "https://developer.huawei.com/consumer/en/hms" + }, + "334": { + "id": 334, + "name": "Akamai MAP", + "creation_date": "2020-11-27", + "code_signature": "com.akamai.android.sdk.AkaMap", + "network_signature": "", + "website": "https://www.akamai.com/" + }, + "336": { + "id": 336, + "name": "Mail.ru", + "creation_date": "2020-11-27", + "code_signature": "ru.mail.mrgservice.advertising|ru.mail.mrgservice.analytics", + "network_signature": "", + "website": "http://mail.ru" + }, + "337": { + "id": 337, + "name": "Airpush", + "creation_date": "2020-12-13", + "code_signature": "com.airpush.", + "network_signature": "api\\.airpush\\.com|apidm\\.airpush\\.com|apistaging\\.airpush\\.com|apportal\\.airpush\\.com|appwall\\.api\\.airpush\\.com|beta\\.airpush\\.com|cdnap\\.airpush\\.com|m\\.airpush\\.com", + "website": "https://airpush.com/" + }, + "338": { + "id": 338, + "name": "Alimama (formerly AdsMogo)", + "creation_date": "2020-12-13", + "code_signature": "com.adsmogo.|com.alimama.", + "network_signature": "\\.alimama\\.|\\.adsmogo\\.", + "website": "https://www.alimama.com/" + }, + "339": { + "id": 339, + "name": "AnySDK", + "creation_date": "2020-12-13", + "code_signature": "com.anysdk.framework.AnalyticsWrapper|com.anysdk.framework.AdsWrapper", + "network_signature": "", + "website": "http://www.anysdk.com/" + }, + "340": { + "id": 340, + "name": "Button", + "creation_date": "2020-12-13", + "code_signature": "com.usebutton.sdk.", + "network_signature": "\\.usebutton.com", + "website": "https://www.usebutton.com" + }, + "341": { + "id": 341, + "name": "Carto (formerly Nutiteq)", + "creation_date": "2020-12-13", + "code_signature": "com.nutiteq|com.carto", + "network_signature": "", + "website": "https://carto.com" + }, + "342": { + "id": 342, + "name": "Didomi", + "creation_date": "2020-12-13", + "code_signature": "io.didomi.sdk.Didomi", + "network_signature": "", + "website": "https://www.didomi.io/" + }, + "343": { + "id": 343, + "name": "JiGuang Aurora Mobile JPush", + "creation_date": "2020-12-13", + "code_signature": "cn.jpush.android", + "network_signature": ".*\\.jiguang\\.cn", + "website": "https://ir.jiguang.cn/corporate-profile" + }, + "344": { + "id": 344, + "name": "Jumio", + "creation_date": "2020-12-13", + "code_signature": "com.jumio.MobileSDK", + "network_signature": "mobile-sdk-resources.jumio.com|nv-sdk.jumio.com", + "website": "https://www.jumio.com/" + }, + "345": { + "id": 345, + "name": "Lenddo", + "creation_date": "2020-12-13", + "code_signature": "com.lenddo.mobile", + "network_signature": "\\.partner-service\\.link", + "website": "https://www.lenddo.com/" + }, + "346": { + "id": 346, + "name": "POKKT", + "creation_date": "2020-12-13", + "code_signature": "com.pokkt.sdk.", + "network_signature": "", + "website": "https://www.pokkt.com" + }, + "347": { + "id": 347, + "name": "Prebid Mobile", + "creation_date": "2020-12-13", + "code_signature": "org.prebid.mobile", + "network_signature": ".prebid.org", + "website": "https://prebid.org" + }, + "348": { + "id": 348, + "name": "SK planet Tad", + "creation_date": "2020-12-13", + "code_signature": "com.skplanet.tad", + "network_signature": "", + "website": "https://www.skplanet.com/eng" + }, + "349": { + "id": 349, + "name": "Split", + "creation_date": "2020-12-13", + "code_signature": "io.split.android.", + "network_signature": "event.split.io|sdk.split.io", + "website": "https://www.split.io/" + }, + "350": { + "id": 350, + "name": "Exponea", + "creation_date": "2021-01-24", + "code_signature": "com.infinario.android.infinariosdk.", + "network_signature": "api.infinario.com|sygic-api.infinario.com", + "website": "https://exponea.com" + }, + "351": { + "id": 351, + "name": "IPQualityScore", + "creation_date": "2021-01-24", + "code_signature": "com.ipqualityscore.", + "network_signature": "ipqualityscore\\.com", + "website": "https://www.ipqualityscore.com" + }, + "352": { + "id": 352, + "name": "Opensignal", + "creation_date": "2021-01-24", + "code_signature": "com.opensignal.datacollection.OpenSignalNdcSdk", + "network_signature": "", + "website": "https://www.opensignal.com" + }, + "353": { + "id": 353, + "name": "SignalFrame", + "creation_date": "2021-01-24", + "code_signature": "com.wirelessregistry.observersdk.", + "network_signature": "", + "website": "https://signalframe.com/" + }, + "354": { + "id": 354, + "name": "X-Mode", + "creation_date": "2021-01-24", + "code_signature": "io.xmode.BcnConfig|io.xmode.locationsdk|io.mysdk.", + "network_signature": "api\\.myendpoint\\.io|bin5y4muil\\.execute-api\\.us-east-1\\.amazonaws\\.com|api\\.smartechmetrics\\.com", + "website": "https://xmode.io/" + } + } +] \ No newline at end of file diff --git a/app/src/main/assets/installers.json b/app/src/main/assets/installers.json new file mode 100755 index 000000000..315f0f798 --- /dev/null +++ b/app/src/main/assets/installers.json @@ -0,0 +1,28 @@ +[ + { + "id": "0", + "title": "Session installer", + "subtitle": "Session based installer for bundled/split APKs", + "description": "Best suited for devices running Android 5.0+.", + "url": "https://developer.android.com/reference/android/content/pm/PackageInstaller.Session" + }, + { + "id": "1", + "title": "Native installer", + "subtitle": "Intent based installer, available on all devices", + "description": "Best suited for devices running below Android 4.4 or OEM modified ROMs like MIUI, One-UI.", + "url": "https://developer.android.com/reference/android/content/Intent#setDataAndType" + }, + { + "id": "2", + "title": "Root installer", + "subtitle": "Installer for background installations", + "description": "Requires root, supports all Android versions." + }, + { + "id": "3", + "title": "Aurora Service", + "subtitle": "Installer for background installations", + "description": "Requires Aurora Services to be installed as system app." + } +] \ No newline at end of file diff --git a/app/src/main/assets/themes.json b/app/src/main/assets/themes.json new file mode 100755 index 000000000..6b5beb7c5 --- /dev/null +++ b/app/src/main/assets/themes.json @@ -0,0 +1,32 @@ +[ + { + "id": "0", + "title": "System", + "subtitle": "Follow system themes." + }, + { + "id": "1", + "title": "Light", + "subtitle": "White UI must die, may slap on your face at night." + }, + { + "id": "2", + "title": "Dark", + "subtitle": "As dark as your humour, suitable for night-owls." + }, + { + "id": "3", + "title": "Pitch Black", + "subtitle": "The black, that matters." + }, + { + "id": "4", + "title": "Dark-X", + "subtitle": "The dark, that looks cool?" + }, + { + "id": "5", + "title": "Disskord", + "subtitle": "Kanged from discord" + } +] \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 000000000..92a70daff Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt new file mode 100644 index 000000000..82f9c3792 --- /dev/null +++ b/app/src/main/java/com/aurora/Constants.kt @@ -0,0 +1,51 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora + +object Constants { + const val INT_EXTRA = "INT_EXTRA" + const val FLOAT_EXTRA = "FLOAT_EXTRA" + const val STRING_APP = "STRING_APP" + const val STRING_EXTRA = "STRING_EXTRA" + const val BROWSE_EXTRA = "BROWSE_EXTRA" + + const val FETCH_GROUP_ID = "FETCH_GROUP_ID" + + const val CONNECTIVITY_CHECK_URL = "https://connectivitycheck.android.com/generate_204" + const val EXODUS_BASE_URL = "https://reports.exodus-privacy.eu.org/api/search/" + const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/" + const val SHARE_URL = "http://play.google.com/store/apps/details?id=" + + const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT" + const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL" + + const val URL_DISPENSER = "http://goolag.store:1337/api/auth" + + //ACCOUNTS + const val ACCOUNT_SIGNED_IN = "ACCOUNT_SIGNED_IN" + const val ACCOUNT_SIGNED_TIMESTAMP = "ACCOUNT_SIGNED_TIMESTAMP" + const val ACCOUNT_TYPE = "ACCOUNT_TYPE" + const val ACCOUNT_EMAIL_PLAIN = "ACCOUNT_EMAIL_PLAIN" + const val ACCOUNT_AAS_PLAIN = "ACCOUNT_AAS_PLAIN" + + const val PAGE_TYPE = "PAGE_TYPE" + const val TOP_CHART_TYPE = "TOP_CHART_TYPE" + const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY" +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/AuroraApplication.kt b/app/src/main/java/com/aurora/store/AuroraApplication.kt new file mode 100644 index 000000000..8aeef42f6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/AuroraApplication.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDexApplication +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.data.receiver.PackageManagerReceiver +import com.aurora.store.data.service.NotificationService +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.PackageUtil +import com.tonyodev.fetch2.Fetch +import nl.komponents.kovenant.android.startKovenant +import nl.komponents.kovenant.android.stopKovenant + +class AuroraApplication : MultiDexApplication() { + + private lateinit var fetch: Fetch + private lateinit var packageManagerReceiver: PackageManagerReceiver + + override fun onCreate() { + super.onCreate() + + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) + + NotificationService.startService(this) + + fetch = DownloadManager.with(this).fetch + + packageManagerReceiver = object : PackageManagerReceiver() { + + } + + //Register broadcast receiver for package install/uninstall + registerReceiver(packageManagerReceiver, PackageUtil.getFilter()) + + NetworkProvider + .with(this) + .bind() + + startKovenant() + + CommonUtil.cleanupInstallationSessions(applicationContext) + } + + override fun onTerminate() { + NetworkProvider + .with(this) + .unbind() + stopKovenant() + super.onTerminate() + } + + override fun onLowMemory() { + NetworkProvider + .with(this) + .unbind() + super.onLowMemory() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/AuroraGlide.kt b/app/src/main/java/com/aurora/store/AuroraGlide.kt new file mode 100644 index 000000000..2f6ea97a4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/AuroraGlide.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +import android.content.Context +import android.graphics.Bitmap +import com.bumptech.glide.GlideBuilder +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory +import com.bumptech.glide.load.engine.cache.LruResourceCache +import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.signature.ObjectKey + +@GlideModule +class AuroraGlide : AppGlideModule() { + override fun applyOptions(context: Context, builder: GlideBuilder) { + val memoryCacheSizeBytes = 1024 * 1024 * 50 + builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong())) + builder.setDiskCache(InternalCacheDiskCacheFactory(context, memoryCacheSizeBytes.toLong())) + builder.setDefaultRequestOptions(requestOptions(context)) + } + + companion object { + private fun requestOptions(context: Context): RequestOptions { + return RequestOptions() + .signature(ObjectKey(System.currentTimeMillis() / (24 * 60 * 60 * 1000))) + .centerCrop() + .encodeFormat(Bitmap.CompressFormat.PNG) + .encodeQuality(60) + .format(DecodeFormat.PREFER_ARGB_8888) + .skipMemoryCache(false) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/Enumerations.kt b/app/src/main/java/com/aurora/store/Enumerations.kt new file mode 100644 index 000000000..b2e72c267 --- /dev/null +++ b/app/src/main/java/com/aurora/store/Enumerations.kt @@ -0,0 +1,25 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +enum class AccountType { + ANONYMOUS, + GOOGLE +} diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt new file mode 100644 index 000000000..606de519a --- /dev/null +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -0,0 +1,243 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +import android.Manifest +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.annotation.IdRes +import androidx.annotation.NonNull +import androidx.core.graphics.ColorUtils +import androidx.core.view.GravityCompat +import androidx.navigation.NavController +import androidx.navigation.NavDestination +import androidx.navigation.Navigation +import androidx.navigation.ui.NavigationUI +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityMainBinding +import com.aurora.store.util.Log +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.ViewUtil.getStyledAttribute +import com.aurora.store.util.extensions.isQAndAbove +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.open +import com.aurora.store.view.ui.about.AboutActivity +import com.aurora.store.view.ui.account.AccountActivity +import com.aurora.store.view.ui.all.AppsGamesActivity +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.commons.BlacklistActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.view.ui.preferences.SettingsActivity +import com.aurora.store.view.ui.sale.AppSalesActivity +import com.aurora.store.view.ui.search.SearchSuggestionActivity +import com.aurora.store.view.ui.spoof.SpoofActivity +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions + + +class MainActivity : BaseActivity() { + + private lateinit var B: ActivityMainBinding + private lateinit var navController: NavController + private lateinit var authData: AuthData + + private var lastBackPressed = 0L + + companion object { + @JvmStatic + private fun matchDestination( + @NonNull destination: NavDestination?, + @IdRes destId: Int + ): Boolean { + var currentDestination = destination + while (currentDestination?.id != destId && currentDestination?.parent != null) { + currentDestination = currentDestination.parent + } + return currentDestination?.id == destId + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityMainBinding.inflate(layoutInflater) + + setContentView(B.root) + + authData = AuthProvider.with(this).getAuthData() + + attachToolbar() + attachNavigation() + attachDrawer() + attachSearch() + + checkPermission() + } + + private fun checkPermission() = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + Log.i("External Storage Access Available") + + if (isQAndAbove()) { + //pickExternalFileDir() + } + } + + private fun attachToolbar() { + B.viewToolbar.imgActionPrimary.setOnClickListener { + B.drawerLayout.openDrawer(GravityCompat.START, true) + } + + B.viewToolbar.imgActionSecondary.setOnClickListener { + val userAppIntent = Intent(this, DownloadActivity::class.java) + startActivity(userAppIntent, ViewUtil.getEmptyActivityBundle(this)) + } + } + + private fun attachSearch() { + B.searchFab.setOnClickListener { + startActivity( + Intent(this, SearchSuggestionActivity::class.java), + ViewUtil.getEmptyActivityBundle(this) + ) + } + } + + private fun attachNavigation() { + val bottomNavigationView: BottomNavigationView = B.navView + navController = Navigation.findNavController(this, R.id.nav_host_fragment) + + val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground) + bottomNavigationView.setBackgroundColor(ColorUtils.setAlphaComponent(backGroundColor, 245)) + + + bottomNavigationView.setOnNavigationItemSelectedListener { item -> + if (item.itemId == bottomNavigationView.selectedItemId) + return@setOnNavigationItemSelectedListener false + NavigationUI.onNavDestinationSelected(item, navController) + true + } + + navController.addOnDestinationChangedListener { _: NavController?, destination: NavDestination?, _: Bundle? -> + val menu: Menu = bottomNavigationView.menu + val size: Int = menu.size() + for (i in 0 until size) { + val item: MenuItem = menu.getItem(i) + if (matchDestination(destination, item.itemId)) { + item.isChecked = true + } + } + } + } + + private fun attachDrawer() { + val headerView: View = B.navigation.getHeaderView(0) + + headerView.let { + it.findViewById(R.id.img)?.load(R.mipmap.ic_launcher) { + transform(RoundedCorners(8)) + } + it.findViewById(R.id.txt_name)?.text = getString(R.string.app_name) + it.findViewById(R.id.txt_email)?.text = + ("v${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}") + } + + B.navigation.setNavigationItemSelectedListener { item: MenuItem -> + when (item.itemId) { + R.id.menu_apps_games -> { + open(AppsGamesActivity::class.java) + } + R.id.menu_apps_sale -> { + open(AppSalesActivity::class.java) + } + R.id.menu_blacklist_manager -> { + open(BlacklistActivity::class.java) + } + R.id.menu_download_manager -> { + open(DownloadActivity::class.java) + } + R.id.menu_spoof_manager -> { + open(SpoofActivity::class.java) + } + R.id.menu_account_manager -> { + open(AccountActivity::class.java) + } + R.id.menu_settings -> { + open(SettingsActivity::class.java) + } + R.id.menu_about -> { + open(AboutActivity::class.java) + } + } + false + } + } + + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp() + } + + override fun onBackPressed() { + if (!navController.navigateUp()) { + if (lastBackPressed + 1000 > System.currentTimeMillis()) { + super.onBackPressed() + } else { + lastBackPressed = System.currentTimeMillis() + Toast.makeText(this, "Click twice to exit", Toast.LENGTH_SHORT).show() + } + } + } + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + private fun pickExternalFileDir() { + val getContentIntent = Intent(Intent.ACTION_GET_CONTENT) + getContentIntent.type = "*/*" + getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + getContentIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true) + getContentIntent.addCategory(Intent.CATEGORY_OPENABLE) + startActivityForResult( + Intent.createChooser( + getContentIntent, + "Aurora Store - External Storage Access" + ), 1337 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/SingletonHolder.kt b/app/src/main/java/com/aurora/store/data/SingletonHolder.kt new file mode 100644 index 000000000..7f2236fd6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/SingletonHolder.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data + +open class SingletonHolder(private val constructor: (A) -> T) { + + @Volatile + private var instance: T? = null + + fun with(arg: A): T { + return when { + instance != null -> instance!! + else -> synchronized(this) { + if (instance == null) instance = constructor(arg) + instance!! + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/State.kt b/app/src/main/java/com/aurora/store/data/State.kt new file mode 100644 index 000000000..732508a41 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/State.kt @@ -0,0 +1,44 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data + + +sealed class ViewState { + object Loading : ViewState() + object Empty : ViewState() + data class Error(val error: String?) : ViewState() + data class Status(val status: String?) : ViewState() + data class Success(val data: T) : ViewState() +} + +sealed class RequestState { + object Init : RequestState() + object Pending : RequestState() + object Complete : RequestState() +} + +sealed class AuthState { + object Available : AuthState() + object Unavailable : AuthState() + object SignedIn : AuthState() + object SignedOut : AuthState() + object Valid : AuthState() + data class Status(val status: String?) : AuthState() +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt new file mode 100644 index 000000000..78539a084 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.downloader + +import android.content.Context +import com.aurora.Constants +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.tonyodev.fetch2.* +import com.tonyodev.fetch2core.DefaultStorageResolver +import com.tonyodev.fetch2core.getFileTempDir + +class DownloadManager private constructor(var context: Context) { + + companion object : SingletonHolder(::DownloadManager) + + var fetch: Fetch + + init { + fetch = Fetch.getInstance(getFetchConfiguration(context)) + } + + fun getFetchInstance(): Fetch { + return fetch + } + + private fun getFetchConfiguration(context: Context): FetchConfiguration { + var maxActive = Preferences.getInteger(context, Preferences.PREFERENCE_DOWNLOAD_ACTIVE) + if (maxActive == 0) + maxActive = 1 + return FetchConfiguration.Builder(context) + .setDownloadConcurrentLimit(maxActive) + .enableLogging(BuildConfig.DEBUG) + .enableHashCheck(true) + .enableFileExistChecks(true) + .enableRetryOnNetworkGain(true) + .enableAutoStart(true) + .setInternetAccessUrlCheck(Constants.CONNECTIVITY_CHECK_URL) + .setAutoRetryMaxAttempts(3) + .setProgressReportingInterval(3000) + .setNamespace(BuildConfig.APPLICATION_ID) + .setStorageResolver(DefaultStorageResolver(context, getFileTempDir(context))) + .build() + } + + fun isDownloading(fetchGroup: FetchGroup): Boolean { + return fetchGroup.downloadingDownloads.isNotEmpty() + || fetchGroup.queuedDownloads.isNotEmpty() + || fetchGroup.addedDownloads.isNotEmpty() + } + + fun isCanceled(fetchGroup: FetchGroup): Boolean { + return fetchGroup.cancelledDownloads.isNotEmpty() + || fetchGroup.removedDownloads.isNotEmpty() + || fetchGroup.deletedDownloads.isNotEmpty() + } + + fun updateOngoingDownloads( + fetch: Fetch, packageList: MutableList, download: Download, + fetchListener: FetchListener? + ) { + if (packageList.contains(download.tag)) { + val packageName = download.tag + if (packageName != null) { + fetch.deleteGroup(packageName.hashCode()) + packageList.remove(packageName) + } + } + if (packageList.size == 0) { + fetch.removeListener(fetchListener!!) + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt b/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt new file mode 100644 index 000000000..c21bdc866 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt @@ -0,0 +1,68 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.downloader + +import android.content.Context +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.store.util.PathUtil +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.EnqueueAction +import com.tonyodev.fetch2.NetworkType +import com.tonyodev.fetch2.Request +import com.tonyodev.fetch2core.Extras +import java.lang.reflect.Modifier + +private inline fun Request.attachMetaData(app: App) { + apply { + groupId = app.id + tag = app.packageName + enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY + networkType = NetworkType.ALL + } +} + +private inline fun Request.attachExtra(app: App) { + val stringMap: MutableMap = mutableMapOf() + val gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + stringMap[Constants.STRING_EXTRA] = gson.toJson(app) + apply { + extras = Extras(stringMap) + } +} + +object RequestBuilder { + + fun buildRequest(context: Context, app: App, file: File): Request { + val fileName = when (file.type) { + File.FileType.BASE, + File.FileType.SPLIT -> PathUtil.getApkDownloadFile(context, app, file) + File.FileType.OBB, + File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file) + } + return Request(file.url, fileName).apply { + attachMetaData(app) + attachExtra(app) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/event/BusEvent.kt b/app/src/main/java/com/aurora/store/data/event/BusEvent.kt new file mode 100644 index 000000000..32acba5e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/event/BusEvent.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.event + +sealed class BusEvent { + data class InstallEvent(var packageName: String, var error: String = String()) : BusEvent() + data class UninstallEvent(var packageName: String, var error: String = String()) : BusEvent() + data class Blacklisted(var packageName: String, var error: String = String()) : BusEvent() + data class GoogleAAS( + var success: Boolean, + var email: String = String(), + var aasToken: String = String() + ) : BusEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt new file mode 100644 index 000000000..dd38a6587 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt @@ -0,0 +1,49 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.os.Build +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID + +open class AppInstaller private constructor(var context: Context) { + + companion object : SingletonHolder(::AppInstaller) + + fun getPreferredInstaller(): IInstaller { + val prefValue = Preferences.getInteger( + context, + PREFERENCE_INSTALLER_ID + ) + + return when (prefValue) { + 1 -> NativeInstaller(context) + 2 -> RootInstaller(context) + 3 -> ServiceInstaller(context) + else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + SessionInstaller(context) + } else { + NativeInstaller(context) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt new file mode 100644 index 000000000..bdabb8999 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt @@ -0,0 +1,29 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +interface IInstaller { + fun install(packageName: String, files: List) + fun uninstall(packageName: String) + + fun clearQueue() + fun isAlreadyQueued(packageName: String): Boolean + fun removeFromInstallQueue(packageName: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt b/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt new file mode 100644 index 000000000..20514489d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt @@ -0,0 +1,70 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import java.io.File + +abstract class InstallerBase(protected var context: Context) : IInstaller { + + private val enqueuedInstalls: MutableSet = mutableSetOf() + + override fun clearQueue() { + enqueuedInstalls.clear() + } + + override fun isAlreadyQueued(packageName: String): Boolean { + return enqueuedInstalls.contains(packageName) + } + + override fun removeFromInstallQueue(packageName: String) { + enqueuedInstalls.remove(packageName) + } + + override fun uninstall(packageName: String) { + val uri = Uri.fromParts("package", packageName, null) + val intent = Intent().apply { + data = uri + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + intent.action = Intent.ACTION_DELETE + } else { + intent.action = Intent.ACTION_UNINSTALL_PACKAGE + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) + } + + context.startActivity(intent) + } + + open fun getUri(file: File): Uri { + return FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt b/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt new file mode 100644 index 000000000..af0a3d2af --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.app.Service +import android.content.Intent +import android.content.pm.PackageInstaller +import android.os.Build +import android.os.IBinder +import androidx.annotation.RequiresApi +import org.apache.commons.lang3.StringUtils + +class InstallerService : Service() { + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) + val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) + + //Send broadcast for the installation status of the package + sendStatusBroadcast(status, packageName) + + //Launch user confirmation activity + if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { + val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + confirmationIntent!!.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + confirmationIntent.putExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME, + "com.android.vending" + ) + confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + try { + startActivity(confirmationIntent) + } catch (e: Exception) { + sendStatusBroadcast(PackageInstaller.STATUS_FAILURE, packageName) + } + } + stopSelf() + return START_NOT_STICKY + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private fun sendStatusBroadcast(status: Int, packageName: String?) { + if (StringUtils.isNotEmpty(packageName)) { + val statusIntent = Intent(ACTION_SESSION_INSTALLER) + statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status) + statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName) + sendBroadcast(statusIntent) + } + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + companion object { + private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt new file mode 100644 index 000000000..c7f825683 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import com.aurora.store.util.Log +import java.io.File + +class NativeInstaller(context: Context) : InstallerBase(context) { + + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + files.map { + when (it) { + is File -> it + is String -> File(it) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + }.forEach { + xInstall(packageName, it) + } + } + } + + private fun xInstall(packageName: String, file: File) { + val intent: Intent + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent = Intent(Intent.ACTION_INSTALL_PACKAGE) + intent.data = getUri(file) + intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK + } else { + intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending") + context.startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt new file mode 100644 index 000000000..438e7aea9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.util.extensions.toast +import com.topjohnwu.superuser.Shell +import java.io.File +import java.util.regex.Pattern + +class RootInstaller(context: Context) : InstallerBase(context) { + + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + if (Shell.getShell().isRoot) { + files.map { + when (it) { + is File -> it + is String -> File(it) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + }.let { + if (isLAndAbove()) + xInstall(packageName, it) + else + xInstallLegacy(packageName, it) + } + } else { + context.toast(context.getString(R.string.installer_root_unavailable)) + Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + } + } + } + + private fun xInstall(packageName: String, files: List) { + var totalSize = 0 + + for (file in files) + totalSize += file.length().toInt() + + val result: Shell.Result = + Shell.su("pm install-create -i com.android.vending --user 0 -r -S $totalSize") + .exec() + + val response = result.out + + val sessionIdPattern = Pattern.compile("(\\d+)") + val sessionIdMatcher = sessionIdPattern.matcher(response[0]) + val found = sessionIdMatcher.find() + + if (found) { + val sessionId = sessionIdMatcher.group(1).toInt() + if (Shell.getShell().isRoot) { + for (file in files) { + Shell.su("cat \"${file.absoluteFile}\" | pm install-write -S ${file.length()} $sessionId \"${file.name}\"") + .exec() + } + + Shell.su("pm install-commit $sessionId").exec() + } else { + removeFromInstallQueue(packageName) + } + } else { + removeFromInstallQueue(packageName) + } + } + + private fun xInstallLegacy(packageName: String, files: List) { + if (Shell.getShell().isRoot) { + Shell.su("pm install -i com.android.vending --user 0 \"${files[0].name}\"").exec() + } else { + removeFromInstallQueue(packageName) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt new file mode 100644 index 000000000..b9dd86cf6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt @@ -0,0 +1,106 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.net.Uri +import android.os.Build +import android.os.IBinder +import android.os.RemoteException +import androidx.annotation.RequiresApi +import com.aurora.services.IPrivilegedCallback +import com.aurora.services.IPrivilegedService +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import java.io.File + +class ServiceInstaller(context: Context) : InstallerBase(context) { + + companion object { + const val ACTION_INSTALL_REPLACE_EXISTING = 2 + const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services" + const val PRIVILEGED_EXTENSION_SERVICE_INTENT = "com.aurora.services.IPrivilegedService" + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + Log.i("Received service install request for $packageName") + val uriList = files.map { + when (it) { + is File -> getUri(it) + is String -> getUri(File(it)) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + } + + xInstall(packageName, uriList) + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun xInstall(packageName: String, uriList: List) { + + val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + val service = IPrivilegedService.Stub.asInterface(binder) + val callback = object : IPrivilegedCallback.Stub() { + override fun handleResult(packageName: String, returnCode: Int) { + removeFromInstallQueue(packageName) + } + } + + try { + service.installSplitPackage( + packageName, + uriList, + ACTION_INSTALL_REPLACE_EXISTING, + BuildConfig.APPLICATION_ID, + callback + ) + } catch (e: RemoteException) { + removeFromInstallQueue(packageName) + Log.e("Failed to connect Aurora Services") + } + } + + override fun onServiceDisconnected(name: ComponentName) { + removeFromInstallQueue(packageName) + Log.e("Disconnected from Aurora Services") + } + } + + val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) + intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) + + context.bindService( + intent, + serviceConnection, + Context.BIND_AUTO_CREATE + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt new file mode 100644 index 000000000..c87138a9a --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt @@ -0,0 +1,119 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.installer + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller.SessionParams +import android.net.Uri +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import org.apache.commons.io.IOUtils +import java.io.File + +class SessionInstaller(context: Context) : InstallerBase(context) { + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + Log.i("Received service install request for $packageName") + val uriList = files.map { + when (it) { + is File -> getUri(it) + is String -> getUri(File(it)) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + } + + xInstall(packageName, uriList) + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun xInstall(packageName: String, uriList: List) { + val packageInstaller = context.packageManager.packageInstaller + val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL) + val sessionId = packageInstaller.createSession(sessionParams) + val session = packageInstaller.openSession(sessionId) + + try { + + Log.i("Writing splits to session for $packageName") + var apkId = 1 + for (uri in uriList) { + val inputStream = context.contentResolver.openInputStream(uri) + val outputStream = session.openWrite( + "${packageName}_${apkId++}", + 0, + -1 + ) + + IOUtils.copy(inputStream, outputStream) + + session.fsync(outputStream) + + IOUtils.close(inputStream) + IOUtils.close(outputStream) + } + + val intent = Intent(context, InstallerService::class.java) + val pendingIntent = PendingIntent.getService( + context, + sessionId, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + Log.i("Starting install session for $packageName") + session.commit(pendingIntent.intentSender) + session.close() + } catch (e: Exception) { + session.abandon() + removeFromInstallQueue(packageName) + Log.e("Failed to install $packageName : %s", e.message) + } + } + + override fun getUri(file: File): Uri { + val uri = FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + + uri.apply { + context.grantUriPermission( + BuildConfig.APPLICATION_ID, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + + return uri + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/model/Accent.kt b/app/src/main/java/com/aurora/store/data/model/Accent.kt new file mode 100644 index 000000000..89971e126 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Accent.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +data class Accent( + var id: Int, + var accent: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Accent -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Black.kt b/app/src/main/java/com/aurora/store/data/model/Black.kt new file mode 100644 index 000000000..93d80ecef --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Black.kt @@ -0,0 +1,40 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +import android.graphics.drawable.Drawable + +data class Black(val packageName: String) { + var displayName: String = String() + var drawable: Drawable? = null + var versionName: String = String() + var versionCode: Int = 0 + + override fun hashCode(): Int { + return packageName.hashCode() + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is Black -> other.packageName == packageName + else -> false + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Dash.kt b/app/src/main/java/com/aurora/store/data/model/Dash.kt new file mode 100644 index 000000000..50d363c2b --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Dash.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +data class Dash( + var id: Int, + var title: String, + var subtitle: String, + var icon: String, + var url: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Dash -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Download.kt b/app/src/main/java/com/aurora/store/data/model/Download.kt new file mode 100644 index 000000000..309e3c5cb --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Download.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +import com.tonyodev.fetch2.Download + +data class DownloadFile(val download: Download) { + + override fun hashCode(): Int { + return download.id + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is DownloadFile -> other.download.status == download.status && other.download.progress == download.progress + else -> false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/model/Exodus.kt b/app/src/main/java/com/aurora/store/data/model/Exodus.kt new file mode 100644 index 000000000..422f43056 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Exodus.kt @@ -0,0 +1,72 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +import java.text.DateFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class ExodusReport { + val creator: String = String() + val name: String = String() + val reports: List = listOf() +} + +class Report { + val id: Int = 0 + val downloads: String = String() + val version: String = String() + val creationDate: String = String() + val updatedAt: String = String() + val versionCode: String = String() + val trackers: List = listOf() + + fun getFormattedCreationDate(): String { + return try { + val simpleDateFormat: DateFormat = SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + Locale.getDefault() + ) + simpleDateFormat.parse(creationDate).toString() + } catch (e: ParseException) { + "" + } + } +} + +class ExodusTracker { + var id: Int = 0 + var name: String = String() + var url: String = String() + var signature: String = String() + var date: String = String() + + override fun hashCode(): Int { + return id + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is ExodusTracker -> other.id == id + else -> false + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Installer.kt b/app/src/main/java/com/aurora/store/data/model/Installer.kt new file mode 100644 index 000000000..64105b826 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Installer.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +data class Installer( + var id: Int, + var title: String, + var subtitle: String, + var description: String, + var url: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Installer -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Link.kt b/app/src/main/java/com/aurora/store/data/model/Link.kt new file mode 100644 index 000000000..b7ad6ed69 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Link.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +data class Link( + var id: Int, + var title: String, + var subtitle: String, + var url: String, + var icon: Int, +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Link -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Theme.kt b/app/src/main/java/com/aurora/store/data/model/Theme.kt new file mode 100644 index 000000000..48dd94570 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Theme.kt @@ -0,0 +1,37 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.model + +data class Theme( + var id: Int, + var title: String, + var subtitle: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Theme -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/network/FuelClient.kt b/app/src/main/java/com/aurora/store/data/network/FuelClient.kt new file mode 100644 index 000000000..72b7621ea --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/FuelClient.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.network + +import com.aurora.gplayapi.GooglePlayApi +import com.aurora.gplayapi.data.models.PlayResponse +import com.aurora.gplayapi.network.IHttpClient +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.core.* +import java.nio.charset.Charset + +object FuelClient : IHttpClient { + + override fun get(url: String, headers: Map): PlayResponse { + return get(url, headers, hashMapOf()) + } + + override fun get( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val parameters = params + .map { it.key to it.value } + .toList() + val (request, response, result) = Fuel.get(url, parameters) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun getAuth(url: String): PlayResponse { + val (request, response, result) = Fuel.get(url) + .appendHeader( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .response() + return buildPlayResponse(response, request) + } + + override fun get( + url: String, + headers: Map, + paramString: String + ): PlayResponse { + val (request, response, result) = Fuel.get(url + paramString) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun post(url: String, headers: Map, body: ByteArray): PlayResponse { + val (request, response, result) = Fuel.post(url) + .header(headers) + .appendHeader(Headers.CONTENT_TYPE, "application/x-protobuf") + .body(body, Charset.defaultCharset()) + .response() + return buildPlayResponse(response, request) + } + + override fun post( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val parameters = params + .map { it.key to it.value } + .toList() + val (request, response, result) = Fuel.post(url, parameters) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun postAuth(url: String, body: ByteArray): PlayResponse { + val (request, response, result) = Fuel.post(url) + .appendHeader( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .body(body) + .response() + return buildPlayResponse(response, request) + } + + @JvmStatic + private fun buildPlayResponse(response: Response, request: Request): PlayResponse { + return PlayResponse().apply { + isSuccessful = response.isSuccessful + code = response.statusCode + + GooglePlayApi + + if (response.isSuccessful) { + responseBytes = response.body().toByteArray() + } + + if (response.isClientError || response.isServerError) { + errorBytes = response.responseMessage.toByteArray() + errorString = String(errorBytes) + } + }.also { + Log.i("FUEL [${request.method}:${response.statusCode}] ${response.url}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/network/HttpClient.kt b/app/src/main/java/com/aurora/store/data/network/HttpClient.kt new file mode 100644 index 000000000..390aa6108 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/HttpClient.kt @@ -0,0 +1,34 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.network + +import android.os.Build +import com.aurora.gplayapi.network.IHttpClient + +object HttpClient { + + fun getPreferredClient(): IHttpClient { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + OkHttpClient + } else { + FuelClient + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt b/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt new file mode 100644 index 000000000..dd8173e7f --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt @@ -0,0 +1,169 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.network + +import com.aurora.gplayapi.data.models.PlayResponse +import com.aurora.gplayapi.network.IHttpClient +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import okhttp3.* +import okhttp3.Headers.Companion.toHeaders +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.util.concurrent.TimeUnit + +object OkHttpClient : IHttpClient { + + private const val POST = "POST" + private const val GET = "GET" + + private val okHttpClient = OkHttpClient().newBuilder() + .connectTimeout(20, TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .followRedirects(true) + .followSslRedirects(true) + .build() + + + @Throws(IOException::class) + fun post(url: String, headers: Map, requestBody: RequestBody): PlayResponse { + val request = Request.Builder() + .url(url) + .headers(headers.toHeaders()) + .method(POST, requestBody) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun post( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val request = Request.Builder() + .url(buildUrl(url, params)) + .headers(headers.toHeaders()) + .method(POST, "".toRequestBody(null)) + .build() + return processRequest(request) + } + + override fun postAuth(url: String, body: ByteArray): PlayResponse { + val requestBody = body.toRequestBody("application/json".toMediaType(), 0, body.size) + val request = Request.Builder() + .url(url) + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .method(POST, requestBody) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun post(url: String, headers: Map, body: ByteArray): PlayResponse { + val requestBody = body.toRequestBody( + "application/x-protobuf".toMediaType(), + 0, + body.size + ) + return post(url, headers, requestBody) + } + + @Throws(IOException::class) + override fun get(url: String, headers: Map): PlayResponse { + return get(url, headers, mapOf()) + } + + @Throws(IOException::class) + override fun get( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val request = Request.Builder() + .url(buildUrl(url, params)) + .headers(headers.toHeaders()) + .method(GET, null) + .build() + return processRequest(request) + } + + override fun getAuth(url: String): PlayResponse { + val request = Request.Builder() + .url(url) + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .method(GET, null) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun get( + url: String, + headers: Map, + paramString: String + ): PlayResponse { + val request = Request.Builder() + .url(url + paramString) + .headers(headers.toHeaders()) + .method(GET, null) + .build() + return processRequest(request) + } + + private fun processRequest(request: Request): PlayResponse { + val call = okHttpClient.newCall(request) + return buildPlayResponse(call.execute()) + } + + private fun buildUrl(url: String, params: Map): HttpUrl { + val urlBuilder = url.toHttpUrl().newBuilder() + params.forEach { + urlBuilder.addQueryParameter(it.key, it.value) + } + return urlBuilder.build() + } + + private fun buildPlayResponse(response: Response): PlayResponse { + return PlayResponse().apply { + isSuccessful = response.isSuccessful + code = response.code + + if (response.body != null) { + responseBytes = response.body!!.bytes() + } + + if (!isSuccessful) { + errorString = response.message + } + }.also { + Log.i("OKHTTP [${response.code}] ${response.request.url}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt b/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt new file mode 100644 index 000000000..895f2d51d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.Constants +import com.aurora.store.AccountType +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences + +class AccountProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::AccountProvider) + + fun isSignedIn(): Boolean { + return Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN) + } + + fun getSignInTimeStamp(): Long { + return Preferences.getLong(context, Constants.ACCOUNT_SIGNED_TIMESTAMP) + } + + fun getAccountType(): AccountType { + val rawType = Preferences.getString(context, Constants.ACCOUNT_TYPE) + return when (rawType) { + "GOOGLE" -> AccountType.GOOGLE + else -> AccountType.ANONYMOUS + } + } + + fun logout() { + Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, false) + Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, "") + Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt b/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt new file mode 100644 index 000000000..0b0b5aa29 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt @@ -0,0 +1,26 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import androidx.core.content.FileProvider + +class ApkProvider : FileProvider() { + +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt b/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt new file mode 100644 index 000000000..113465107 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +class AuthProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::AuthProvider) + + private var gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + + fun getAuthData(): AuthData { + return getSavedAuthData() + } + + private fun getSavedAuthData(): AuthData { + Log.i("Loading saved AuthData") + + val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA) + return if (rawAuth.isNotEmpty()) + gson.fromJson(rawAuth, AuthData::class.java) + else + AuthData("", "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt b/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt new file mode 100644 index 000000000..a2d5364e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import java.lang.reflect.Modifier + +class BlacklistProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::BlacklistProvider) { + const val PREFERENCE_BLACKLIST = "PREFERENCE_BLACKLIST" + } + + private var gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + + fun getBlackList(): MutableSet { + val rawBlacklist = Preferences.getString(context, PREFERENCE_BLACKLIST) + return try { + if (rawBlacklist.isEmpty()) + mutableSetOf() + else + gson.fromJson(rawBlacklist, object : TypeToken?>() {}.type) + } catch (e: Exception) { + mutableSetOf() + } + } + + fun isBlacklisted(packageName: String): Boolean { + return getBlackList().contains(packageName) + } + + fun blacklist(packageName: String) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.add(packageName) + save(oldBlackList) + } + + fun whitelist(packageName: String) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.remove(packageName) + save(oldBlackList) + } + + fun blacklist(packageNames: Set) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.addAll(packageNames) + save(oldBlackList) + } + + @Synchronized + fun save(blacklist: Set) { + Preferences.putString(context, PREFERENCE_BLACKLIST, gson.toJson(blacklist)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt new file mode 100644 index 000000000..bdeea7cac --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt @@ -0,0 +1,131 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.opengl.GLES10 +import android.text.TextUtils +import java.util.* +import javax.microedition.khronos.egl.EGL10 +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.egl.EGLContext +import javax.microedition.khronos.egl.EGLDisplay + +object EglExtensionProvider { + @JvmStatic + val eglExtensions: List + get() { + val glExtensions: MutableSet = HashSet() + val egl10 = EGLContext.getEGL() as EGL10 + val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) + egl10.eglInitialize(display, IntArray(2)) + val cf = IntArray(1) + if (egl10.eglGetConfigs(display, null, 0, cf)) { + val configs = arrayOfNulls(cf[0]) + if (egl10.eglGetConfigs(display, configs, cf[0], cf)) { + val a1 = intArrayOf( + EGL10.EGL_WIDTH, + EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_HEIGHT, + EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_NONE + ) + val a2 = intArrayOf(12440, EGL10.EGL_PIXMAP_BIT, EGL10.EGL_NONE) + val a3 = IntArray(1) + for (i in 0 until cf[0]) { + egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, a3) + if (a3[0] != EGL10.EGL_SLOW_CONFIG) { + egl10.eglGetConfigAttrib( + display, + configs[i], + EGL10.EGL_SURFACE_TYPE, + a3 + ) + if (1 and a3[0] != 0) { + egl10.eglGetConfigAttrib( + display, + configs[i], + EGL10.EGL_RENDERABLE_TYPE, + a3 + ) + if (1 and a3[0] != 0) { + addExtensionsForConfig( + egl10, + display, + configs[i], + a1, + null, + glExtensions + ) + } + if (4 and a3[0] != 0) { + addExtensionsForConfig( + egl10, + display, + configs[i], + a1, + a2, + glExtensions + ) + } + } + } + } + } + } + egl10.eglTerminate(display) + val sorted: List = ArrayList(glExtensions) + Collections.sort(sorted) + return sorted + } + + private fun addExtensionsForConfig( + egl10: EGL10, + eglDisplay: EGLDisplay, + eglConfig: EGLConfig?, + ai: IntArray, + ai1: IntArray?, + set: MutableSet + ) { + val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1) + if (eglContext === EGL10.EGL_NO_CONTEXT) { + return + } + val eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, eglConfig, ai) + if (eglSurface === EGL10.EGL_NO_SURFACE) { + egl10.eglDestroyContext(eglDisplay, eglContext) + } else { + egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) + val s = GLES10.glGetString(7939) + if (!TextUtils.isEmpty(s)) { + val `as` = s.split(" ".toRegex()).toTypedArray() + val i = `as`.size + set.addAll(listOf(*`as`).subList(0, i)) + } + egl10.eglMakeCurrent( + eglDisplay, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT + ) + egl10.eglDestroySurface(eglDisplay, eglSurface) + egl10.eglDestroyContext(eglDisplay, eglContext) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt b/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt new file mode 100644 index 000000000..a18f80d9e --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import org.json.JSONArray +import org.json.JSONObject +import java.nio.charset.StandardCharsets + +class ExodusDataProvider private constructor(val context: Context) { + + companion object : SingletonHolder(::ExodusDataProvider) + + private val exodusTrackers: JSONObject + + init { + exodusTrackers = loadLocalTrackers() + } + + fun getLocalTrackers(): JSONObject { + return exodusTrackers + } + + fun getFilteredTrackers(trackerIds: List): List { + return trackerIds.map { + exodusTrackers.getJSONObject( + it.toString() + ) + }.toList() + } + + private fun loadLocalTrackers(): JSONObject { + val inputStream = context.assets.open("exodus_trackers.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + val jsonArray = JSONArray(json) + return jsonArray.getJSONObject(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt new file mode 100644 index 000000000..c59fc0f85 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt @@ -0,0 +1,167 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.providers + +import android.app.ActivityManager +import android.content.Context +import android.content.ContextWrapper +import android.content.res.Configuration +import android.os.Build +import android.text.TextUtils +import java.util.* + +class NativeDeviceInfoProvider(context: Context) : ContextWrapper(context) { + + fun getNativeDeviceProperties(): Properties { + return Properties().apply { + //Build Props + setProperty("UserReadableName", Build.DEVICE) + setProperty("Build.HARDWARE", Build.HARDWARE) + setProperty( + "Build.RADIO", + if (Build.getRadioVersion() != null) + Build.getRadioVersion() + else + "unknown" + ) + setProperty("Build.FINGERPRINT", Build.FINGERPRINT) + setProperty("Build.BRAND", Build.BRAND) + setProperty("Build.DEVICE", Build.DEVICE) + setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") + setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) + setProperty("Build.MODEL", Build.MODEL) + setProperty("Build.MANUFACTURER", Build.MANUFACTURER) + setProperty("Build.PRODUCT", Build.PRODUCT) + setProperty("Build.ID", Build.ID) + setProperty("Build.BOOTLOADER", Build.BOOTLOADER) + + val config = applicationContext.resources.configuration + setProperty("TouchScreen", "${config.touchscreen}") + setProperty("Keyboard", "${config.keyboard}") + setProperty("Navigation", "${config.navigation}") + setProperty("ScreenLayout", "${config.screenLayout and 15}") + setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") + setProperty( + "HasFiveWayNavigation", + "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" + ) + + //Display Metrics + val metrics = applicationContext.resources.displayMetrics + setProperty("Screen.Density", "${metrics.densityDpi}") + setProperty("Screen.Width", "${metrics.widthPixels}") + setProperty("Screen.Height", "${metrics.heightPixels}") + + + //Supported Platforms + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) + } else { + val platform = mutableListOf() + if (!TextUtils.isEmpty(Build.CPU_ABI)) { + platform.add(Build.CPU_ABI) + } + if (!TextUtils.isEmpty(Build.CPU_ABI2)) { + platform.add(Build.CPU_ABI2) + } + + setProperty("Platforms", platform.joinToString(separator = ",")) + } + //Supported Features + setProperty("Features", getFeatures().joinToString(separator = ",")) + //Shared Locales + setProperty("Locales", getLocales().joinToString(separator = ",")) + //Shared Libraries + setProperty("SharedLibraries", getSharedLibraries().joinToString(separator = ",")) + //GL Extensions + val activityManager = + applicationContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager + setProperty( + "GL.Version", + activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() + ) + setProperty( + "GL.Extensions", + EglExtensionProvider.eglExtensions.joinToString(separator = ",") + ) + + //Google Related Props + val gsfVersionProvider = NativeGsfVersionProvider(applicationContext) + setProperty("Client", "android-google") + setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") + setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode(true)}") + setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString(true)) + + //MISC + setProperty("Roaming", "mobile-notroaming") + setProperty("TimeZone", "UTC-10") + + //Telephony (USA 3650 AT&T) + setProperty("CellOperator", "310") + setProperty("SimOperator", "38") + } + } + + private fun getFeatures(): List { + val featureStringList: MutableList = ArrayList() + try { + val availableFeatures = applicationContext.packageManager.systemAvailableFeatures + for (feature in availableFeatures) { + if (feature.name.isNotEmpty()) { + featureStringList.add(feature.name) + } + } + } catch (e: Exception) { + + } + return featureStringList + } + + private fun getLocales(): List { + val localeList: MutableList = ArrayList() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + localeList.addAll(listOf(*applicationContext.assets.locales)) + } else { + for (locale in Locale.getAvailableLocales()) { + localeList.add(locale.toString()) + } + } + val locales: MutableList = ArrayList() + for (locale in localeList) { + if (TextUtils.isEmpty(locale)) { + continue + } + locales.add(locale.replace("-", "_")) + } + return locales + } + + private fun getSharedLibraries(): List { + val systemSharedLibraryNames = applicationContext.packageManager.systemSharedLibraryNames + val libraries: MutableList = ArrayList() + try { + if (systemSharedLibraryNames != null) { + libraries.addAll(listOf(*systemSharedLibraryNames)) + } + } catch (e: Exception) { + + } + return libraries + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt new file mode 100644 index 000000000..d62e43b0d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import android.content.pm.PackageManager + +class NativeGsfVersionProvider(context: Context) { + private var gsfVersionCode = 0 + private var vendingVersionCode = 0 + private var vendingVersionString = "" + + init { + try { + gsfVersionCode = + context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + // com.google.android.gms not found + } + try { + val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0) + vendingVersionCode = packageInfo.versionCode + vendingVersionString = packageInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + // com.android.vending not found + } + } + + init { + try { + gsfVersionCode = + context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + // com.google.android.gms not found + } + try { + val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0) + vendingVersionCode = packageInfo.versionCode + vendingVersionString = packageInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + // com.android.vending not found + } + } + + fun getGsfVersionCode(defaultIfNotFound: Boolean): Int { + return if (defaultIfNotFound && gsfVersionCode < GOOGLE_SERVICES_VERSION_CODE) + GOOGLE_SERVICES_VERSION_CODE + else + gsfVersionCode + } + + fun getVendingVersionCode(defaultIfNotFound: Boolean): Int { + return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE) + GOOGLE_VENDING_VERSION_CODE + else + vendingVersionCode + } + + fun getVendingVersionString(defaultIfNotFound: Boolean): String { + return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE) + GOOGLE_VENDING_VERSION_STRING + else + vendingVersionString + } + + companion object { + private const val GOOGLE_SERVICES_PACKAGE_ID = "com.google.android.gms" + private const val GOOGLE_VENDING_PACKAGE_ID = "com.android.vending" + private const val GOOGLE_SERVICES_VERSION_CODE = 203019037 + private const val GOOGLE_VENDING_VERSION_CODE = 82151710 + private const val GOOGLE_VENDING_VERSION_STRING = "21.5.17-21 [0] [PR] 326734551" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt new file mode 100644 index 000000000..4d7150cf1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt @@ -0,0 +1,101 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.novoda.merlin.Merlin + +class NetworkProvider(var context: Context) { + + companion object : SingletonHolder(::NetworkProvider) { + + private var networkListeners: MutableList = mutableListOf() + + fun addListener(networkListener: NetworkListener) { + Log.i("Network-Provider added to ${networkListener.javaClass.simpleName}") + networkListeners.add(networkListener) + } + + fun removeListener(networkListener: NetworkListener) { + Log.i("Network-Provider removed from ${networkListener.javaClass.simpleName}") + networkListeners.remove(networkListener) + } + } + + private var merlin: Merlin = Merlin.Builder() + .withAllCallbacks() + .build(context) + + private var isDisconnected = true + + fun bind() { + merlin.bind() + + merlin.registerConnectable { + if (isDisconnected) { + isDisconnected = false + onReConnected() + } else { + onConnected() + } + } + + merlin.registerDisconnectable { + isDisconnected = true + onDisconnected() + } + } + + fun unbind() { + networkListeners.clear() + merlin.unbind() + Log.i("Network-Provider destroyed") + } + + private fun onConnected() { + Log.i("Network-Provider connected") + isDisconnected = false + networkListeners.forEach { + it.onConnected() + } + } + + private fun onReConnected() { + Log.i("Network-Provider reconnected") + networkListeners.forEach { + it.onReconnected() + } + } + + private fun onDisconnected() { + Log.e("Network-Provider disconnected") + networkListeners.forEach { + it.onDisconnected() + } + } + + interface NetworkListener { + fun onConnected() + fun onDisconnected() + fun onReconnected() + } +} diff --git a/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt b/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt new file mode 100644 index 000000000..527c34fb4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.BuildConfig +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.aurora.store.util.PathUtil +import java.io.BufferedInputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.util.* +import java.util.jar.JarEntry +import java.util.jar.JarFile + +class SpoofDeviceProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::SpoofDeviceProvider) { + private const val SUFFIX = ".properties" + + fun filenameValid(filename: String): Boolean { + return filename.endsWith(SUFFIX) + } + } + + val availableDevice: List + get() { + val propertiesList: MutableList = ArrayList() + propertiesList.add(0, NativeDeviceInfoProvider(context).getNativeDeviceProperties()) + propertiesList.addAll(spoofDevicesFromApk) + propertiesList.addAll(spoofDevicesFromUser) + return propertiesList + } + + private val spoofDevicesFromApk: List + get() { + val jarFile = apkAsJar + val propertiesList: MutableList = ArrayList() + if (null == jarFile) { + return propertiesList + } + val entries = jarFile.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + if (!filenameValid(entry.name)) { + continue + } + propertiesList.add(getProperties(jarFile, entry)) + } + return propertiesList + } + + private val spoofDevicesFromUser: List + get() { + val deviceNames: MutableList = ArrayList() + val defaultDir = File(PathUtil.getExternalPath()) + val files = defaultDir.listFiles() + if (defaultDir.exists() && files != null) { + for (file in files) { + if (!file.isFile || !filenameValid(file.name)) { + continue + } + deviceNames.add(getProperties(file)) + } + } + return deviceNames + } + + private fun getProperties(jarFile: JarFile, entry: JarEntry): Properties { + val properties = Properties() + try { + properties.load(jarFile.getInputStream(entry)) + properties.setProperty("CONFIG_NAME", entry.name) + } catch (e: IOException) { + Log.e("Could not read %s", entry.name) + } + return properties + } + + private fun getProperties(file: File): Properties { + val properties = Properties() + try { + properties.load(BufferedInputStream(FileInputStream(file))) + properties.setProperty("CONFIG_NAME", file.name) + } catch (e: IOException) { + Log.e("Could not read %s", file.name) + } + return properties + } + + private val devicesFromApk: Map + get() { + val deviceNames: MutableMap = HashMap() + val jarFile = apkAsJar ?: return deviceNames + + val entries = jarFile.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + if (!filenameValid(entry.name)) { + continue + } + + deviceNames[entry.name] = + getProperties(jarFile, entry).getProperty("UserReadableName") + } + return deviceNames + } + + private val apkAsJar: JarFile? + get() { + val file = apkFile + try { + if (file != null && file.exists()) { + return JarFile(file) + } + } catch (e: IOException) { + Log.e("Could not open Aurora Store apk as a jar file") + } + return null + } + + private val apkFile: File? + get() { + try { + val sourceDir: String = context.packageManager.getApplicationInfo( + BuildConfig.APPLICATION_ID, + 0 + ).sourceDir + + if (sourceDir.isNotEmpty()) { + return File(sourceDir) + } + } catch (ignored: Exception) { + + } + return null + } + + private val devicesFromDownloadDirectory: Map + get() { + val deviceNames: MutableMap = HashMap() + val defaultDir = File(PathUtil.getExternalPath()) + if (!defaultDir.exists() || null == defaultDir.listFiles()) { + return deviceNames + } + for (file in defaultDir.listFiles()) { + if (!file.isFile || !filenameValid(file.name)) { + continue + } + val name = getProperties(file).getProperty("UserReadableName") + if (name != null) { + deviceNames[file.name] = name + } + } + return deviceNames + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt b/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt new file mode 100644 index 000000000..084687423 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt @@ -0,0 +1,87 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier +import java.util.* + +class SpoofProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::SpoofProvider) { + const val LOCALE_SPOOF_ENABLED = "LOCALE_SPOOF_ENABLED" + const val LOCALE_SPOOF_LANG = "LOCALE_SPOOF_LANG" + const val LOCALE_SPOOF_COUNTRY = "LOCALE_SPOOF_COUNTRY" + + const val DEVICE_SPOOF_ENABLED = "DEVICE_SPOOF_ENABLED" + const val DEVICE_SPOOF_PROPERTIES = "DEVICE_SPOOF_PROPERTIES" + } + + fun isLocaleSpoofEnabled(): Boolean { + return Preferences.getBoolean(context, LOCALE_SPOOF_ENABLED) + } + + fun isDeviceSpoofEnabled(): Boolean { + return Preferences.getBoolean(context, DEVICE_SPOOF_ENABLED) + } + + fun getSpoofLocale(): Locale { + return if (isLocaleSpoofEnabled()) { + Locale( + Preferences.getString(context, LOCALE_SPOOF_LANG), + Preferences.getString(context, LOCALE_SPOOF_COUNTRY) + ) + } else { + Locale.getDefault() + } + } + + fun getSpoofDeviceProperties(): Properties { + return if (isDeviceSpoofEnabled()) { + val gson: Gson = + GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + return gson.fromJson( + Preferences.getString(context, DEVICE_SPOOF_PROPERTIES), + Properties::class.java + ) + } else { + Properties() + } + } + + fun setSpoofLocale(locale: Locale) { + Preferences.putBoolean(context, LOCALE_SPOOF_ENABLED, true) + Preferences.putString(context, LOCALE_SPOOF_LANG, locale.language) + Preferences.putString(context, LOCALE_SPOOF_COUNTRY, locale.country) + } + + fun setSpoofDeviceProperties(properties: Properties) { + val gson: Gson = + GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + Preferences.putBoolean(context, DEVICE_SPOOF_ENABLED, true) + Preferences.putString(context, DEVICE_SPOOF_PROPERTIES, gson.toJson(properties)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt new file mode 100644 index 000000000..3e21e49c9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt @@ -0,0 +1,27 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class BootReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt new file mode 100644 index 000000000..228c7f4c1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadCancelReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .cancelGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt new file mode 100644 index 000000000..fc4ee03e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadPauseReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .pauseGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt new file mode 100644 index 000000000..5ea777e37 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadResumeReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .resumeGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt new file mode 100644 index 000000000..2ebba3d56 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class InstallReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + /*val packageName = extras.getString(Constants.INTENT_PACKAGE_NAME, "") + val versionString = extras.getString(Constants.DOWNLOAD_VERSION_CODE) + if (!packageName.isEmpty() && versionString != null) { + AuroraApplication.getInstaller().installSplit(packageName, versionString.toInt()) + }*/ + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt new file mode 100644 index 000000000..8e2703657 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.receiver + +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.store.data.event.BusEvent.InstallEvent +import com.aurora.store.data.event.BusEvent.UninstallEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.util.PathUtil +import com.aurora.store.util.Preferences +import org.greenrobot.eventbus.EventBus +import java.io.File + +open class PackageManagerReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != null && intent.data != null) { + val packageName = intent.data!!.encodedSchemeSpecificPart + + when (intent.action) { + Intent.ACTION_PACKAGE_ADDED -> { + EventBus.getDefault() + .post(InstallEvent(packageName, "")) + + //Clear installation queue + AppInstaller.with(context) + .getPreferredInstaller() + .removeFromInstallQueue(packageName) + } + Intent.ACTION_PACKAGE_REMOVED -> EventBus.getDefault() + .post(UninstallEvent(packageName, "")) + } + + clearNotification(context, packageName) + + val isAutoDeleteAPKEnabled = Preferences.getBoolean( + context, + Preferences.PREFERENCE_AUTO_DELETE + ) + + if (isAutoDeleteAPKEnabled) + clearDownloads(context, packageName) + } + } + + private fun clearNotification(context: Context, packageName: String) { + val notificationManager = context.applicationContext + .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(packageName, packageName.hashCode()) + } + + private fun clearDownloads(context: Context, packageName: String) { + try { + val rootDirPath = PathUtil.getPackageDirectory(context, packageName) + val rootDir = File(rootDirPath) + if (rootDir.exists()) + rootDir.deleteRecursively() + } catch (e: Exception) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt new file mode 100644 index 000000000..6c2948e07 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class UpdatesReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/service/NotificationService.kt b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt new file mode 100644 index 000000000..2dd2b7946 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt @@ -0,0 +1,378 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.service + +import android.app.* +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.IBinder +import android.util.ArrayMap +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.receiver.DownloadCancelReceiver +import com.aurora.store.data.receiver.DownloadPauseReceiver +import com.aurora.store.data.receiver.DownloadResumeReceiver +import com.aurora.store.data.receiver.InstallReceiver +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.* +import org.apache.commons.lang3.StringUtils +import java.lang.reflect.Modifier +import java.util.* + +class NotificationService : Service() { + + companion object { + fun startService(context: Context) { + try { + context.startService(Intent(context, NotificationService::class.java)) + } catch (e: Exception) { + Log.e("Failed to start notification service : %s", e.message) + } + } + } + + private lateinit var fetch: Fetch + private lateinit var fetchListener: AbstractFetchGroupListener + private lateinit var notificationManager: NotificationManager + + private val appMap = ArrayMap() + private val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return START_NOT_STICKY + } + + override fun onCreate() { + super.onCreate() + + Log.i("Notification Service Started") + + notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + + //Create Notification Channels : General & Alert + createNotificationChannel() + + fetch = DownloadManager.with(this).fetch + + fetchListener = object : AbstractFetchGroupListener() { + override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + } + + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + if (fetchGroup.groupDownloadProgress == 100) { + install(download.tag!!, fetchGroup.downloads) + } + } + + override fun onError( + groupId: Int, + download: Download, + error: Error, + throwable: Throwable?, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onProgress( + groupId: Int, + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onQueued( + groupId: Int, + download: Download, + waitingNetwork: Boolean, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + } + } + + fetch.addListener(fetchListener) + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channels = ArrayList() + channels.add( + NotificationChannel( + Constants.NOTIFICATION_CHANNEL_ALERT, + getString(R.string.notification_channel_alert), + NotificationManager.IMPORTANCE_HIGH + ) + ) + channels.add( + NotificationChannel( + Constants.NOTIFICATION_CHANNEL_GENERAL, + getString(R.string.notification_channel_general), + NotificationManager.IMPORTANCE_MIN + ) + ) + notificationManager.createNotificationChannels(channels) + } + } + + private fun showNotification(groupId: Int, download: Download, fetchGroup: FetchGroup) { + val status = download.status + + //Ignore notifications for completion of sub-parts of a bundled apk + if (status == Status.COMPLETED && fetchGroup.groupDownloadProgress < 100) + return + + //synchronized(appMap) { + var app: App? = appMap[groupId] + + if (app == null) { + app = gson.fromJson( + download.extras.getString(Constants.STRING_EXTRA, "{}"), + App::class.java + ) + appMap[groupId] = app + } + + if (app == null) + return + + val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL) + builder.setContentTitle(app.displayName) + builder.setSmallIcon(R.drawable.ic_notification_outlined) + builder.color = ContextCompat.getColor(this, R.color.colorAccent) + builder.setWhen(download.created) + builder.setContentIntent(getContentIntentForDownloads()) + + when (status) { + Status.PAUSED -> { + builder.setSmallIcon(R.drawable.ic_download_pause) + builder.setContentText(getString(R.string.download_paused)) + } + Status.CANCELLED -> { + builder.setSmallIcon(R.drawable.ic_download_cancel) + builder.setContentText(getString(R.string.download_canceled)) + builder.color = Color.RED + } + Status.FAILED -> { + builder.setSmallIcon(R.drawable.ic_download_fail) + builder.setContentText(getString(R.string.download_failed)) + builder.color = Color.RED + } + Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) { + builder.setSmallIcon(android.R.drawable.stat_sys_download_done) + builder.setContentText(getString(R.string.download_completed)) + } + else -> { + builder.setSmallIcon(android.R.drawable.stat_sys_download) + builder.setContentText(getString(R.string.download_metadata)) + } + } + val progress = fetchGroup.groupDownloadProgress + val progressBigText = NotificationCompat.BigTextStyle() + when (status) { + + Status.QUEUED -> { + builder.setProgress(100, 0, true) + progressBigText.bigText(getString(R.string.download_queued)) + builder.setStyle(progressBigText) + } + + Status.DOWNLOADING -> { + val contentString = getString(R.string.download_progress) + val partString = StringUtils.joinWith( + "/", + fetchGroup.completedDownloads.size + 1, + fetchGroup.downloads.size + ) + val speedString: String = + CommonUtil.humanReadableByteSpeed(download.downloadedBytesPerSecond, true) + progressBigText.bigText( + StringUtils.joinWith( + " \u2022 ", + contentString, + partString, + speedString + ) + ) + builder.setStyle(progressBigText) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_pause, + getString(R.string.action_pause), + getPauseIntent(groupId) + ).build() + ) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_cancel, + getString(R.string.action_cancel), + getCancelIntent(groupId) + ).build() + ) + if (progress < 0) builder.setProgress( + 100, + 0, + true + ) else builder.setProgress(100, progress, false) + } + + Status.PAUSED -> { + val pauseString = getString(R.string.download_paused) + val filesString = StringUtils.joinWith( + "/", + fetchGroup.completedDownloads.size, + fetchGroup.downloads.size + ) + progressBigText.bigText( + StringUtils.joinWith( + " \u2022 ", + pauseString, + filesString + ) + ) + builder.setStyle(progressBigText) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_pause, + getString(R.string.action_resume), + getResumeIntent(groupId) + ).build() + ) + } + + Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) { + builder.setAutoCancel(true) + builder.setContentIntent(getContentIntentForDetails(app)) + builder.setStyle(progressBigText) + } + else -> { + + } + } + + if (isLAndAbove()) { + when (status) { + Status.DOWNLOADING -> builder.setCategory(Notification.CATEGORY_PROGRESS) + Status.FAILED, Status.CANCELLED -> builder.setCategory(Notification.CATEGORY_ERROR) + else -> builder.setCategory(Notification.CATEGORY_STATUS) + } + } + + notificationManager.notify( + app.packageName, + app.id, + builder.build() + ) + //} + } + + private fun getPauseIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadPauseReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getResumeIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadResumeReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getCancelIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadCancelReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getContentIntentForDetails(app: App?): PendingIntent { + val intent = Intent(this, AppDetailsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + return PendingIntent.getActivity( + this, + packageName.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + private fun getContentIntentForDownloads(): PendingIntent { + val intent = Intent(this, DownloadActivity::class.java) + return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getInstallIntent(packageName: String, versionCode: String): PendingIntent { + val intent = Intent(this, InstallReceiver::class.java) + intent.putExtra(Constants.STRING_EXTRA, packageName) + return PendingIntent.getBroadcast( + this, + packageName.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + @Synchronized + private fun install(packageName: String, files: List) { + AppInstaller.with(this) + .getPreferredInstaller() + .install( + packageName, + files + .filter { it.file.endsWith(".apk") } + .map { + it.file + }.toList() + ) + } + + override fun onDestroy() { + Log.i("Notification Service Stopped") + fetch.removeListener(fetchListener) + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/AC2DMTask.kt b/app/src/main/java/com/aurora/store/util/AC2DMTask.kt new file mode 100644 index 000000000..334431cbb --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/AC2DMTask.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import com.github.kittinunf.fuel.Fuel +import java.util.* + +class AC2DMTask { + @Throws(Exception::class) + fun getAC2DMResponse(email: String?, oAuthToken: String?): Map { + if (email == null || oAuthToken == null) + return mapOf() + + val params: MutableMap = hashMapOf() + params["lang"] = Locale.getDefault().toString().replace("_", "-") + params["google_play_services_version"] = PLAY_SERVICES_VERSION_CODE + params["sdk_version"] = BUILD_VERSION_SDK + params["device_country"] = Locale.getDefault().country.toLowerCase(Locale.US) + params["Email"] = email + params["service"] = "ac2dm" + params["get_accountid"] = 1 + params["ACCESS_TOKEN"] = 1 + params["callerPkg"] = "com.google.android.gms" + params["add_account"] = 1 + params["Token"] = oAuthToken + params["callerSig"] = "38918a453d07199354f8b19af05ec6562ced5788" + + val body = params.map { "${it.key}=${it.value}" }.joinToString(separator = "&") + + val response = Fuel.post(TOKEN_AUTH_URL) + .body(body) + .header("app" to "com.google.android.gms") + .header("User-Agent" to "") + .header("Content-Type" to "application/x-www-form-urlencoded") + .response() + + return response.third.fold(success = { + Util.parseResponse(String(it)) + }, failure = { + mapOf() + }) + } + + companion object { + private const val TOKEN_AUTH_URL = "https://android.clients.google.com/auth" + private const val BUILD_VERSION_SDK = 28 + private const val PLAY_SERVICES_VERSION_CODE = 19629032 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/ApkCopier.kt b/app/src/main/java/com/aurora/store/util/ApkCopier.kt new file mode 100644 index 000000000..c1f690735 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/ApkCopier.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import com.aurora.store.util.extensions.isLAndAbove +import org.apache.commons.io.IOUtils +import java.io.File +import java.io.FileOutputStream +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +class ApkCopier(private val context: Context, private val packageName: String) { + + fun copy() { + val destination = File(PathUtil.getBaseCopyDirectory()) + + destination.let { + if (it.exists()) { + Log.i("Base copy directory is available") + } else { + it.mkdirs() + Log.e("Base copy directory is created : ${it.path}") + } + } + + val packageInfo: PackageInfo = context.packageManager.getPackageInfo( + packageName, + PackageManager.GET_META_DATA + ) + + val baseApk = getBaseApk(packageInfo) + val fileList: MutableList = mutableListOf() + + /*Add base APK*/ + fileList.add(baseApk) + + if (isLAndAbove()) { + val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs + if (splitSourceDirs != null && splitSourceDirs.isNotEmpty()) { + /*Add Split APKs*/ + fileList.addAll(getSplitAPKs(packageInfo)) + } + bundleAllAPKs(fileList) + } else { + bundleAllAPKs(fileList) + } + } + + private fun getBaseApk(packageInfo: PackageInfo?): File? { + return if (packageInfo?.applicationInfo != null) { + File(packageInfo.applicationInfo.sourceDir) + } else { + null + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun getSplitAPKs(packageInfo: PackageInfo): MutableList { + val fileList: MutableList = ArrayList() + val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs + if (splitSourceDirs != null) { + for (fileName in splitSourceDirs) fileList.add(File(fileName)) + } + return fileList + } + + private fun bundleAllAPKs(fileList: List) { + try { + val fileOutputStream = + FileOutputStream(PathUtil.getBaseCopyDirectory() + "$packageName.zip") + val zipOutputStream = ZipOutputStream(fileOutputStream) + + for (file in fileList) { + file?.let { + val zipEntry = ZipEntry(file.name) + zipOutputStream.putNextEntry(zipEntry) + IOUtils.copy(it.inputStream(), zipOutputStream) + zipOutputStream.closeEntry() + } + } + + zipOutputStream.close() + } catch (e: Exception) { + e.printStackTrace() + Log.e("ApkCopier : %s", e.message) + } + } + + fun getUri(file: File): Uri { + return FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/CertUtil.kt b/app/src/main/java/com/aurora/store/util/CertUtil.kt new file mode 100644 index 000000000..e2d18353e --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/CertUtil.kt @@ -0,0 +1,100 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.pm.PackageManager +import com.aurora.store.util.extensions.isPAndAbove +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.* + +object CertUtil { + private const val FDROID = "FDROID" + private const val GUARDIAN = "GUARDIANPROJECT.INFO" + + private fun getX509Certificates( + context: Context, + packageName: String + ): List { + val certificates: MutableList = mutableListOf() + val packageManager = context.applicationContext.packageManager + + try { + + val packageInfo = if (isPAndAbove()) + packageManager.getPackageInfo( + packageName, + PackageManager.GET_SIGNING_CERTIFICATES + ) + else + packageManager.getPackageInfo( + packageName, + PackageManager.GET_SIGNATURES + ) + + val certificateFactory = CertificateFactory.getInstance("X509") + + if (isPAndAbove()) { + packageInfo.signingInfo.apkContentsSigners.forEach { + val bytes = it.toByteArray() + val inputStream: InputStream = ByteArrayInputStream(bytes) + certificates.add( + certificateFactory!!.generateCertificate(inputStream) as X509Certificate + ) + } + } else { + for (i in 0..packageInfo.signatures.size) { + val bytes = packageInfo.signatures[i].toByteArray() + val inStream: InputStream = ByteArrayInputStream(bytes) + certificates.add( + certificateFactory!!.generateCertificate(inStream) as X509Certificate + ) + } + } + } catch (e: Exception) { + Log.e(e.message) + } + + return certificates + } + + fun isFDroidApp(context: Context, packageName: String): Boolean { + val certificates = getX509Certificates(context, packageName) + + return if (certificates.isEmpty()) + false + else { + val cert = certificates[0] + if (cert != null) { + if (cert.subjectDN != null) { + val DN = cert.subjectDN.name.toUpperCase(Locale.getDefault()) + DN.contains(FDROID) || DN.contains(GUARDIAN) + } else { + false + } + } else { + false + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/CommonUtil.kt b/app/src/main/java/com/aurora/store/util/CommonUtil.kt new file mode 100644 index 000000000..467a2f0f5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/CommonUtil.kt @@ -0,0 +1,189 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.os.Build +import android.os.Bundle +import androidx.core.app.ActivityOptionsCompat +import com.aurora.store.R +import java.text.DecimalFormat +import java.util.* +import kotlin.math.ln +import kotlin.math.pow + +object CommonUtil { + + private val siPrefixes: Map = hashMapOf( + Pair(1, ""), + Pair(3, " KB"), + Pair(6, " MB"), + Pair(9, " GB") + ) + private val diPrefixes: Map = hashMapOf( + Pair(1, ""), + Pair(3, " K"), + Pair(6, " Million"), + Pair(9, " Billion") + ) + + fun addSiPrefix(value: Long): String { + if (value <= 0L) + return "NA" + var tempValue = value + var order = 0 + while (tempValue >= 1000.0) { + tempValue /= 1000.toLong() + order += 3 + } + return tempValue.toString() + siPrefixes[order] + } + + fun addDiPrefix(value: Long): String { + if (value <= 0L) + return "NA" + var tempValue = value + var order = 0 + while (tempValue >= 1000.0) { + tempValue /= 1000.0.toLong() + order += 3 + } + return tempValue.toString() + diPrefixes[order] + } + + fun getETAString(context: Context, etaInMilliSeconds: Long): String { + if (etaInMilliSeconds < 0) { + return context.getString(R.string.download_eta_calculating) + } + var seconds = (etaInMilliSeconds / 1000).toInt() + val hours = (seconds / 3600).toLong() + seconds -= (hours * 3600).toInt() + val minutes = (seconds / 60).toLong() + seconds -= (minutes * 60).toInt() + return when { + hours > 0 -> { + context.getString(R.string.download_eta_hrs, hours, minutes, seconds) + } + minutes > 0 -> { + context.getString(R.string.download_eta_min, minutes, seconds) + } + else -> { + context.getString(R.string.download_eta_sec, seconds) + } + } + } + + fun humanReadableByteSpeed(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) return "$bytes B" + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i" + return String.format( + Locale.getDefault(), "%.1f %sB/s", + bytes / unit.toDouble().pow(exp.toDouble()), + pre + ) + } + + fun humanReadableByteValue(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) return "$bytes B" + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i" + return String.format( + Locale.getDefault(), "%.1f %sB", + bytes / unit.toDouble().pow(exp.toDouble()), + pre + ) + } + + fun getDownloadSpeedString(context: Context, downloadedBytesPerSecond: Long): String { + if (downloadedBytesPerSecond < 0) { + return context.getString(R.string.download_speed_estimating) + } + val kb = downloadedBytesPerSecond.toDouble() / 1000.toDouble() + val mb = kb / 1000.toDouble() + val decimalFormat = DecimalFormat(".##") + return when { + mb >= 1 -> { + context.getString(R.string.download_speed_mb, decimalFormat.format(mb)) + } + kb >= 1 -> { + context.getString(R.string.download_speed_kb, decimalFormat.format(kb)) + } + else -> { + context.getString(R.string.download_speed_bytes, downloadedBytesPerSecond) + } + } + } + + fun getEmptyActivityBundle(context: Context): Bundle? { + return ActivityOptionsCompat.makeCustomAnimation( + context, + android.R.anim.fade_in, + android.R.anim.fade_out + ).toBundle() + } + + fun cleanupInstallationSessions(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val packageInstaller = context.packageManager.packageInstaller + for (sessionInfo in packageInstaller.mySessions) { + try { + val sessionId = sessionInfo.sessionId + packageInstaller.abandonSession(sessionInfo.sessionId) + Log.i("Abandoned session id -> %d", sessionId) + } catch (e: Exception) { + + } + } + } + } + + fun getThemeStyleById(themeId: Int): Int { + return when (themeId) { + 0 -> R.style.AppTheme + 1 -> R.style.AppTheme_Light + 2 -> R.style.AppTheme_Dark + 3 -> R.style.AppTheme_Black + 4 -> R.style.AppTheme_DarkX + 5 -> R.style.AppTheme_Darkord + else -> R.style.AppTheme + } + } + + fun getAccentStyleById(accentId: Int): Int { + return when (accentId) { + 1 -> R.style.Accent01 + 2 -> R.style.Accent02 + 3 -> R.style.Accent03 + 4 -> R.style.Accent04 + 5 -> R.style.Accent05 + 6 -> R.style.Accent06 + 7 -> R.style.Accent07 + 8 -> R.style.Accent08 + 9 -> R.style.Accent09 + 10 -> R.style.Accent10 + 11 -> R.style.Accent11 + 12 -> R.style.Accent12 + else -> R.style.Accent01 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Log.kt b/app/src/main/java/com/aurora/store/util/Log.kt new file mode 100644 index 000000000..c6959642d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Log.kt @@ -0,0 +1,73 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.util.Log +import java.io.File +import java.io.FileWriter +import java.io.IOException + +object Log { + + const val TAG = "¯\\_(ツ)_/¯ " + + fun e(message: String?, vararg args: Any?) { + e(String.format(message!!, *args)) + } + + fun e(message: String?) { + Log.e(TAG, message!!) + } + + fun i(message: String?, vararg args: Any?) { + i(String.format(message!!, *args)) + } + + fun i(message: String?) { + Log.i(TAG, message!!) + } + + fun d(message: String?, vararg args: Any?) { + d(String.format(message!!, *args)) + } + + fun d(message: String?) { + Log.d(TAG, message!!) + } + + fun w(message: String?, vararg args: Any?) { + w(String.format(message!!, *args)) + } + + fun w(message: String?) { + Log.w(TAG, message!!) + } + + fun writeToFile(context: Context, obj: Any) { + try { + val out = FileWriter(File(context.filesDir, "AuroraLogs.txt")) + out.write(obj.toString()) + out.close() + } catch (e: IOException) { + e(e.message) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/NavigationUtil.kt b/app/src/main/java/com/aurora/store/util/NavigationUtil.kt new file mode 100644 index 000000000..ebb516723 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/NavigationUtil.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.app.ActivityOptions +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.appcompat.app.AppCompatActivity +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.model.Report +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.details.DetailsExodusActivity +import com.aurora.store.view.ui.details.DevAppsActivity +import com.aurora.store.view.ui.search.SearchResultsActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +object NavigationUtil { + val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + fun openDetailsActivity(context: Context, app: App) { + val intent = Intent( + context, + AppDetailsActivity::class.java + ).apply { + putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openDevAppsActivity(context: Context, app: App) { + val intent = Intent( + context, + DevAppsActivity::class.java + ).apply { + putExtra(Constants.STRING_APP, gson.toJson(app)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openExodusActivity(context: Context, app: App, report: Report) { + val intent = Intent( + context, + DetailsExodusActivity::class.java + ).apply { + putExtra(Constants.STRING_APP, gson.toJson(app)) + putExtra(Constants.STRING_EXTRA, gson.toJson(report)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openSearchActivity(context: Context) { + val intent = Intent( + context, + SearchResultsActivity::class.java + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/PackageUtil.kt b/app/src/main/java/com/aurora/store/util/PackageUtil.kt new file mode 100644 index 000000000..a6f80058d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/PackageUtil.kt @@ -0,0 +1,155 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.os.Build + + +object PackageUtil { + + fun isInstalled(context: Context, packageName: String): Boolean { + return try { + context.packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + fun isUpdatable(context: Context, packageName: String, versionCode: Long): Boolean { + return try { + val packageInfo = getPackageInfo(context, packageName) + if (packageInfo != null) { + return versionCode > packageInfo.versionCode + } + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + fun isTv(context: Context): Boolean { + val uiMode = context.resources.configuration.uiMode + return uiMode and Configuration.UI_MODE_TYPE_MASK == Configuration.UI_MODE_TYPE_TELEVISION + } + + fun getLaunchIntent(context: Context, packageName: String?): Intent? { + val isTv = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTv(context) + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (isTv) { + context.packageManager.getLeanbackLaunchIntentForPackage(packageName!!) + } else { + context.packageManager.getLaunchIntentForPackage(packageName!!) + } + } else { + context.packageManager.getLaunchIntentForPackage(packageName!!) + } + + return if (intent == null) { + null + } else { + intent.addCategory(if (isTv) Intent.CATEGORY_LEANBACK_LAUNCHER else Intent.CATEGORY_LAUNCHER) + intent + } + } + + @Throws(Exception::class) + fun getPackageInfo(context: Context, packageName: String?): PackageInfo? { + return context.packageManager.getPackageInfo(packageName!!, 0) + } + + fun getAllPackages(context: Context): List { + val packageInfoSet: MutableList = mutableListOf() + val packageManager: PackageManager = context.packageManager + val flags: Int = getAllFlags() + val packageInfoList: List = packageManager.getInstalledPackages(flags) + for (packageInfo in packageInfoList) { + if (packageInfo.packageName != null && packageInfo.applicationInfo != null) { + packageInfoSet.add(packageInfo) + } + } + return packageInfoSet + } + + fun getPackageInfoMap(context: Context): MutableMap { + val packageInfoSet: MutableMap = mutableMapOf() + val packageManager: PackageManager = context.packageManager + val flags: Int = PackageManager.GET_META_DATA + var packageInfoList: List = packageManager.getInstalledPackages(flags) + + val isFdroidFilterEnabled = Preferences.getBoolean( + context, + Preferences.PREFERENCE_FILTER_FDROID + ) + + packageInfoList = packageInfoList + .filter { + it.packageName != null && it.applicationInfo != null && it.applicationInfo.enabled + } + + if (isFdroidFilterEnabled) { + packageInfoList + .filter { + val packageInstaller = packageManager.getInstallerPackageName(it.packageName) + packageInstaller != "org.fdroid.fdroid.privileged" + }.filter { + !CertUtil.isFDroidApp(context, it.packageName) + } + } + + packageInfoList.forEach { + packageInfoSet[it.packageName] = it + } + + return packageInfoSet + } + + + fun getFilter(): IntentFilter { + val filter = IntentFilter() + filter.addDataScheme("package") + filter.addAction(Intent.ACTION_PACKAGE_INSTALL) + filter.addAction(Intent.ACTION_PACKAGE_ADDED) + filter.addAction(Intent.ACTION_PACKAGE_REMOVED) + return filter + } + + private fun getAllFlags(): Int { + var flags = (PackageManager.GET_META_DATA + or PackageManager.GET_ACTIVITIES + or PackageManager.GET_SERVICES + or PackageManager.GET_PROVIDERS + or PackageManager.GET_RECEIVERS) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + flags = flags or PackageManager.GET_DISABLED_COMPONENTS + flags = flags or PackageManager.GET_UNINSTALLED_PACKAGES + } else { + flags = flags or PackageManager.MATCH_DISABLED_COMPONENTS + flags = flags or PackageManager.MATCH_UNINSTALLED_PACKAGES + } + return flags + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/PathUtil.kt b/app/src/main/java/com/aurora/store/util/PathUtil.kt new file mode 100644 index 000000000..2d442982a --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/PathUtil.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.os.Environment +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.store.util.extensions.isLAndAbove + +fun Context.getInternalBaseDirectory(): String { + return filesDir.path +} + +object PathUtil { + + private fun getDownloadDirectory(context: Context): String { + return if (isLAndAbove()) + context.getInternalBaseDirectory() + "/Downloads" + else + getExternalPath() + } + + fun getPackageDirectory(context: Context, packageName: String): String { + return getDownloadDirectory(context) + "/$packageName" + } + + private fun getVersionDirectory( + context: Context, + packageName: String, + versionCode: Int + ): String { + return getPackageDirectory(context, packageName) + "/$versionCode" + } + + fun getApkDownloadFile(context: Context, app: App, file: File): String { + return getVersionDirectory(context, app.packageName, app.versionCode) + "/${file.name}" + } + + fun getApkDownloadFile(context: Context, packageName: String, versionCode: Int): String { + return getVersionDirectory(context, packageName, versionCode) + } + + fun getExternalPath(): String { + return Environment.getExternalStorageDirectory().toString() + "/Aurora/" + } + + fun getBaseCopyDirectory(): String { + return "${getExternalPath()}/files/export/" + } + + private fun getObbDownloadPath(context: Context, app: App): String { + return Environment.getExternalStorageDirectory() + .toString() + "/Android/obb/" + app.packageName + } + + fun getObbDownloadFile(context: Context, app: App, file: File): String { + val obbDir = getObbDownloadPath(context, app) + return "$obbDir/${file.name}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt new file mode 100644 index 000000000..f84cb7e9d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.SharedPreferences +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager + +object Preferences { + + const val PREFERENCE_AUTH_DATA = "PREFERENCE_AUTH_DATA" + const val PREFERENCE_INSTALLER_ID = "PREFERENCE_INSTALLER_ID" + const val PREFERENCE_THEME_TYPE = "PREFERENCE_THEME_TYPE" + const val PREFERENCE_THEME_ACCENT = "PREFERENCE_THEME_ACCENT" + const val PREFERENCE_INTRO = "PREFERENCE_INTRO" + + const val PREFERENCE_FILTER_GOOGLE = "PREFERENCE_FILTER_GOOGLE" + const val PREFERENCE_FILTER_FDROID = "PREFERENCE_FILTER_FDROID" + + const val PREFERENCE_AUTO_INSTALL = "PREFERENCE_AUTO_INSTALL" + const val PREFERENCE_AUTO_DELETE = "PREFERENCE_AUTO_DELETE" + + const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION" + + const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE" + const val PREFERENCE_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI" + + + private fun getPrefs(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } + + fun putString(context: Context, key: String, value: String) { + getPrefs(context).edit().putString(key, value).apply() + } + + fun putInteger(context: Context, key: String, value: Int) { + getPrefs(context).edit().putInt(key, value).apply() + } + + fun putFloat(context: Context, key: String, value: Float) { + getPrefs(context).edit().putFloat(key, value).apply() + } + + fun putLong(context: Context, key: String, value: Long) { + getPrefs(context).edit().putLong(key, value).apply() + } + + fun putBoolean(context: Context, key: String, value: Boolean) { + getPrefs(context).edit().putBoolean(key, value).apply() + } + + fun getString(context: Context, key: String): String { + return getPrefs(context).getString(key, "").toString() + } + + fun getInteger(context: Context, key: String): Int { + return getPrefs(context).getInt(key, 0) + } + + fun getFloat(context: Context, key: String): Float { + return getPrefs(context).getFloat(key, 0.0f) + } + + fun getLong(context: Context, key: String): Long { + return getPrefs(context).getLong(key, 0L) + } + + fun getBoolean(context: Context, key: String): Boolean { + return getPrefs(context).getBoolean(key, false) + } +} + +fun Context.save(key: String, value: Int) { + Preferences.putInteger(this, key, value) +} + +fun Fragment.save(key: String, value: Int) { + Preferences.putInteger(requireContext(), key, value) +} + +fun Context.save(key: String, value: Boolean) { + Preferences.putBoolean(this, key, value) +} + +fun Fragment.save(key: String, value: Boolean) { + Preferences.putBoolean(requireContext(), key, value) +} + +fun Context.save(key: String, value: String) { + Preferences.putString(this, key, value) +} + +fun Fragment.save(key: String, value: String) { + Preferences.putString(requireContext(), key, value) +} diff --git a/app/src/main/java/com/aurora/store/util/Util.java b/app/src/main/java/com/aurora/store/util/Util.java new file mode 100644 index 000000000..e08648db5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Util.java @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Util { + + public static Map parseResponse(String response) { + Map keyValueMap = new HashMap(); + StringTokenizer st = new StringTokenizer(response, "\n\r"); + while (st.hasMoreTokens()) { + String[] keyValue = st.nextToken().split("=", 2); + if (keyValue.length >= 2) { + keyValueMap.put(keyValue[0], keyValue[1]); + } + } + return keyValueMap; + } + + public static Map parseCookieString(String cookies) { + Map cookieList = new HashMap<>(); + Pattern cookiePattern = Pattern.compile("([^=]+)=([^;]*);?\\s?"); + Matcher matcher = cookiePattern.matcher(cookies); + while (matcher.find()) { + String cookieKey = matcher.group(1); + String cookieValue = matcher.group(2); + cookieList.put(cookieKey, cookieValue); + } + return cookieList; + } +} diff --git a/app/src/main/java/com/aurora/store/util/ViewUtil.kt b/app/src/main/java/com/aurora/store/util/ViewUtil.kt new file mode 100644 index 000000000..2bf47cb31 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/ViewUtil.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util + +import android.app.Activity +import android.content.Context +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityOptionsCompat + + +object ViewUtil { + + fun getEmptyActivityBundle(context: Context?): Bundle? { + return ActivityOptionsCompat.makeCustomAnimation( + context!!, + android.R.anim.fade_in, + android.R.anim.fade_out + ).toBundle() + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun configureActivityLayout(activity: Activity, isLight: Boolean) { + val window = activity.window + val params = window.attributes + params.flags = params.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + window.attributes = params + window.statusBarColor = Color.TRANSPARENT + setFullScreenLightStatusBar(activity, isLight) + } + + private fun setFullScreenLightStatusBar(activity: Activity, isLight: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + var flags = activity.window.decorView.systemUiVisibility + if (isLight) + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + activity.window.decorView.systemUiVisibility = flags + } + } + + fun getStyledAttribute(context: Context, styleID: Int): Int { + val arr = context.obtainStyledAttributes(TypedValue().data, intArrayOf(styleID)) + val styledColor = arr.getColor(0, Color.WHITE) + arr.recycle() + return styledColor + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Collection.kt b/app/src/main/java/com/aurora/store/util/extensions/Collection.kt new file mode 100644 index 000000000..6acdca890 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Collection.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +fun MutableList.flushAndAdd(list: List) { + clear() + addAll(list) +} + +fun MutableSet.flushAndAdd(list: Set) { + clear() + addAll(list) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Commons.kt b/app/src/main/java/com/aurora/store/util/extensions/Commons.kt new file mode 100644 index 000000000..f0ee7dbe0 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Commons.kt @@ -0,0 +1,46 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.os.Build +import android.text.format.DateFormat +import java.util.* + +fun Long.toDate(): String { + val calendar = Calendar.getInstance(Locale.getDefault()) + calendar.timeInMillis = this + return DateFormat.format("dd/MM/yy", calendar).toString() +} + +fun isLAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP +} + +fun isNAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N +} + +fun isPAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P +} + +fun isQAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Context.kt b/app/src/main/java/com/aurora/store/util/extensions/Context.kt new file mode 100644 index 000000000..7c509c7f8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Context.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ShareCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.util.ViewUtil +import kotlin.system.exitProcess + + +fun Context.browse(url: String) { + try { + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(url) + ) + ) + } catch (e: Exception) { + Log.e(e.message) + } +} + +fun Context.share(app: App) { + try { + ShareCompat.IntentBuilder.from(this as AppCompatActivity) + .setType("text/plain") + .setChooserTitle(getString(R.string.action_share)) + .setSubject(app.displayName) + .setText(Constants.SHARE_URL + app.packageName) + .startChooser() + } catch (e: Exception) { + + } +} + +fun Context.openInfo(packageName: String) { + try { + val intent = Intent( + "android.settings.APPLICATION_DETAILS_SETTINGS", + Uri.parse("package:$packageName") + ) + startActivity(intent) + } catch (e: Exception) { + + } +} + +fun Context.open(className: Class, newTask: Boolean = false) { + val intent = Intent(this, className) + if (newTask) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) +} + +fun AppCompatActivity.close() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + finishAfterTransition() + } else { + finish() + } +} + +fun Context.restartApp() { + val pendingIntent = PendingIntent.getActivity( + this, + 1337, + Intent(this, MainActivity::class.java), + PendingIntent.FLAG_CANCEL_CURRENT + ) + + val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent) + exitProcess(0) +} + +fun Context.copyToClipBoard(data: String?) { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Download Url", data) + clipboard.setPrimaryClip(clip) +} diff --git a/app/src/main/java/com/aurora/store/util/extensions/Glide.kt b/app/src/main/java/com/aurora/store/util/extensions/Glide.kt new file mode 100644 index 000000000..048e8c1d1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Glide.kt @@ -0,0 +1,165 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.annotation.RawRes +import com.bumptech.glide.Glide +import com.bumptech.glide.TransitionOptions +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.ViewTarget +import com.bumptech.glide.request.transition.DrawableCrossFadeFactory +import java.io.File + +fun ImageView.load( + bitmap: Bitmap?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(bitmap, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + byteArray: ByteArray?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(byteArray, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + drawable: Drawable?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(drawable, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + @RawRes @DrawableRes resourceId: Int?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(resourceId, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + uri: Uri?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(uri, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + string: String?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(string, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + file: File?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(file, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + bitmap: Bitmap?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(bitmap, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + byteArray: ByteArray?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(byteArray, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + drawable: Drawable?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(drawable, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + @RawRes @DrawableRes resourceId: Int?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(resourceId, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + uri: Uri?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(uri, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + string: String?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(string, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + file: File?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(file, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.loadAny( + data: Any?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget { + return Glide.with(this) + .load(data) + .apply { + transitionOptions?.let { transition(it) } + requestOptions?.let { apply(it) } + } + .into(this) +} + +@JvmSynthetic +inline fun ImageView.loadAny( + data: Any?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget { + val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build() + return Glide.with(this) + .load(data) + .transition(withCrossFade(factory)) + .apply(RequestOptions().apply(requestOptions)) + .into(this) +} + +@JvmSynthetic +fun ImageView.clear() { + //Glide.with(this).clear(this) +} diff --git a/app/src/main/java/com/aurora/store/util/extensions/Number.kt b/app/src/main/java/com/aurora/store/util/extensions/Number.kt new file mode 100644 index 000000000..d58817d19 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Number.kt @@ -0,0 +1,28 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.content.res.Resources + +val Number.dp: Number + get() = (this.toFloat() / Resources.getSystem().displayMetrics.density).toInt() + +val Number.px: Number + get() = (this.toFloat() * Resources.getSystem().displayMetrics.density).toInt() \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt b/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt new file mode 100644 index 000000000..b4c6f13a6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt @@ -0,0 +1,148 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Intent +import android.content.res.Configuration +import android.graphics.Color +import android.os.Build +import android.view.View +import android.view.WindowInsetsController +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.graphics.ColorUtils +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.store.R +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.ViewUtil + + +fun Fragment.applyTheme( + themeId: Int, + accentId: Int = 1, + shouldApplyTransition: Boolean = true, + position: Int = 2 +) { + val themeStyle = CommonUtil.getThemeStyleById(themeId) + + if (themeStyle == 0) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + (requireActivity() as AppCompatActivity).applyDayNightMask() + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + /*Apply Theme*/ + requireContext().theme.applyStyle(themeStyle, true) + + /*Apply transition only on AppCompatActivity*/ + if (shouldApplyTransition) + (requireActivity() as AppCompatActivity).transitionRecreate(position) + else + (requireActivity() as AppCompatActivity).recreate() + + if (themeId == 1) { + (requireActivity() as AppCompatActivity).setLightConfiguration() + } +} + +fun AppCompatActivity.transitionRecreate(position: Int = 2) { + val intent = Intent(this, javaClass) + intent.putExtra(Constants.INT_EXTRA, position) + //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + overridePendingTransition(R.anim.fade_in, R.anim.fade_out) +} + +fun AppCompatActivity.applyTheme(themeId: Int, accentId: Int = 1) { + val themeStyle = CommonUtil.getThemeStyleById(themeId) + val accentStyle = CommonUtil.getAccentStyleById(accentId) + + if (themeStyle == 0) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + applyDayNightMask() + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + /*Apply Theme*/ + setTheme(themeStyle) + + /*Apply Accent*/ + theme.applyStyle(accentStyle, true) + + /*Apply Light Configuration*/ + if (themeId == 1) { + setLightConfiguration() + } +} + +fun AppCompatActivity.applyDayNightMask() { + val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) { + setLightConfiguration() + } +} + +fun AppCompatActivity.setLightConfiguration() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setLightConfigurationO() + } else { + setLightConfigurationO() + } +} + +@RequiresApi(Build.VERSION_CODES.R) +private fun AppCompatActivity.setLightConfigurationR() { + window?.insetsController?.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + ) +} + +private fun AppCompatActivity.setLightConfigurationO() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setLightStatusBar() + setLightNavigationBar() + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.statusBarColor = ColorUtils.setAlphaComponent(Color.BLACK, 120) + } +} + +private fun AppCompatActivity.setLightStatusBar() { + var flags = window.decorView.systemUiVisibility + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + window.decorView.systemUiVisibility = flags +} + +private fun AppCompatActivity.setLightNavigationBar() { + var flags = window.decorView.systemUiVisibility + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + window.navigationBarColor = + ViewUtil.getStyledAttribute(this, android.R.attr.colorBackground) + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } + window.decorView.systemUiVisibility = flags +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Threading.kt b/app/src/main/java/com/aurora/store/util/extensions/Threading.kt new file mode 100644 index 000000000..8a6ef856d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Threading.kt @@ -0,0 +1,41 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.os.Handler +import android.os.Looper + +fun runAsync(action: () -> Unit) = Thread(Runnable(action)).start() + +fun runOnUiThread(action: () -> Unit) { + if (isMainLooperAlive()) { + action() + } else { + Handler(Looper.getMainLooper()).post(Runnable(action)) + } +} + +fun runDelayed(delayMillis: Long, action: () -> Unit) = + Handler().postDelayed(Runnable(action), delayMillis) + +fun runDelayedOnUiThread(delayMillis: Long, action: () -> Unit) = + Handler(Looper.getMainLooper()).postDelayed(Runnable(action), delayMillis) + +private fun isMainLooperAlive() = Looper.myLooper() == Looper.getMainLooper() \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Toast.kt b/app/src/main/java/com/aurora/store/util/extensions/Toast.kt new file mode 100644 index 000000000..b83289a6e --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Toast.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Context +import android.widget.Toast +import androidx.annotation.StringRes + +fun Context.toast(text: CharSequence): Toast = + Toast.makeText(this, text, Toast.LENGTH_SHORT).apply { show() } + +fun Context.longToast(text: CharSequence): Toast = + Toast.makeText(this, text, Toast.LENGTH_LONG).apply { show() } + +fun Context.toast(@StringRes resId: Int): Toast = + Toast.makeText(this, resId, Toast.LENGTH_SHORT).apply { show() } + +fun Context.longToast(@StringRes resId: Int): Toast = + Toast.makeText(this, resId, Toast.LENGTH_LONG).apply { show() } diff --git a/app/src/main/java/com/aurora/store/util/extensions/View.kt b/app/src/main/java/com/aurora/store/util/extensions/View.kt new file mode 100644 index 000000000..06284e99f --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/View.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Context +import android.view.View +import android.view.inputmethod.InputMethodManager + +fun View.isVisible() = visibility == View.VISIBLE + +fun View.isGone() = visibility == View.GONE + +fun View.isInvisible() = visibility == View.INVISIBLE + +fun View.show() { + visibility = View.VISIBLE +} + +fun View.hide() { + visibility = View.GONE +} + +fun View.showKeyboard() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + this.requestFocus() + imm.showSoftInput(this, 0) +} + +fun View.hideKeyboard(): Boolean { + try { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + return imm.hideSoftInputFromWindow(windowToken, 0) + } catch (ignored: RuntimeException) { + } + return false +} + +fun View.setOnSingleClickListener(tolerance: Long = 500, onClick: (v: View) -> Unit) { + var lastClicked = 0L + val currentTimeMillis = System.currentTimeMillis() + setOnClickListener { + if (currentTimeMillis - lastClicked > tolerance) { + onClick(it) + lastClicked = currentTimeMillis + } + } +} + +fun View.rotate(resetToZero: Boolean = true, duration: Long = 400) { + if (resetToZero) + rotation = 0f + animate().rotation(360f).setDuration(duration).start() +} + +fun View.flip(resetToZero: Boolean = true, duration: Long = 400) { + if (resetToZero) + rotation = 0f + animate().rotation(180f).setDuration(duration).start() +} + +fun View.getString(resourceId: Int): String { + return context.getString(resourceId) +} + diff --git a/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt b/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt new file mode 100644 index 000000000..53bbe5ed6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt @@ -0,0 +1,86 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionButtonBinding + +class ActionButton : RelativeLayout { + + private lateinit var B: 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) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_action_button, this) + B = ViewActionButtonBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton) + val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText) + B.btn.text = btnTxt + typedArray.recycle() + } + + fun setText(text: String) { + B.btn.text = text + } + + fun updateProgress(isVisible: Boolean) { + if (isVisible) + B.progress.visibility = View.VISIBLE + else + + B.progress.visibility = View.INVISIBLE + } + + fun addOnClickListener(onClickListener: OnClickListener) { + B.btn.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java b/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java new file mode 100644 index 000000000..ad993a9df --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java @@ -0,0 +1,94 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom; + +import android.graphics.PointF; +import android.view.animation.Interpolator; + +public class CubicBezierInterpolator implements Interpolator { + + public static final CubicBezierInterpolator DEFAULT = new CubicBezierInterpolator(0.25, 0.1, 0.25, 1); + public static final CubicBezierInterpolator EASE_OUT = new CubicBezierInterpolator(0, 0, .58, 1); + public static final CubicBezierInterpolator EASE_OUT_QUINT = new CubicBezierInterpolator(.23, 1, .32, 1); + public static final CubicBezierInterpolator EASE_IN = new CubicBezierInterpolator(.42, 0, 1, 1); + public static final CubicBezierInterpolator EASE_BOTH = new CubicBezierInterpolator(.42, 0, .58, 1); + public static final CubicBezierInterpolator EASE_IN_OUT_QUAD = new CubicBezierInterpolator(0.455, 0.03, 0.515, 0.955); + + protected PointF start; + protected PointF end; + protected PointF a = new PointF(); + protected PointF b = new PointF(); + protected PointF c = new PointF(); + + public CubicBezierInterpolator(PointF start, PointF end) throws IllegalArgumentException { + if (start.x < 0 || start.x > 1) { + throw new IllegalArgumentException("startX value must be in the range [0, 1]"); + } + if (end.x < 0 || end.x > 1) { + throw new IllegalArgumentException("endX value must be in the range [0, 1]"); + } + this.start = start; + this.end = end; + } + + public CubicBezierInterpolator(float startX, float startY, float endX, float endY) { + this(new PointF(startX, startY), new PointF(endX, endY)); + } + + public CubicBezierInterpolator(double startX, double startY, double endX, double endY) { + this((float) startX, (float) startY, (float) endX, (float) endY); + } + + @Override + public float getInterpolation(float time) { + return getBezierCoordinateY(getXForTime(time)); + } + + protected float getBezierCoordinateY(float time) { + c.y = 3 * start.y; + b.y = 3 * (end.y - start.y) - c.y; + a.y = 1 - c.y - b.y; + return time * (c.y + time * (b.y + time * a.y)); + } + + protected float getXForTime(float time) { + float x = time; + float z; + for (int i = 1; i < 14; i++) { + z = getBezierCoordinateX(x) - time; + if (Math.abs(z) < 1e-3) { + break; + } + x -= z / getXDerivate(x); + } + return x; + } + + private float getXDerivate(float t) { + return c.x + t * (2 * b.x + 3 * a.x * t); + } + + private float getBezierCoordinateX(float time) { + c.x = 3 * start.x; + b.x = 3 * (end.x - start.x) - c.x; + a.x = 1 - c.x - b.x; + return time * (c.x + time * (b.x + time * a.x)); + } +} diff --git a/app/src/main/java/com/aurora/store/view/custom/RatingView.kt b/app/src/main/java/com/aurora/store/view/custom/RatingView.kt new file mode 100644 index 000000000..33ccf0c67 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/RatingView.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewRatingBinding + +class RatingView : RelativeLayout { + + private lateinit var B: ViewRatingBinding + + var number = 0 + var max = 0 + var rating = 0 + + constructor(context: Context, number: Int, max: Int, rating: Int) : super(context) { + this.number = number + this.max = max + this.rating = rating + init(context) + } + + constructor(context: Context?) : super(context) {} + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {} + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + } + + private fun init(context: Context) { + val view = inflate(context, R.layout.view_rating, this) + B = ViewRatingBinding.bind(view) + + B.avgNum.text = number.toString() + B.avgRating.max = max + B.avgRating.progress = rating + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/StateButton.kt b/app/src/main/java/com/aurora/store/view/custom/StateButton.kt new file mode 100644 index 000000000..91b05954f --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/StateButton.kt @@ -0,0 +1,89 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import com.aurora.store.R +import com.aurora.store.databinding.ViewStateButtonBinding + +class StateButton : RelativeLayout { + + private lateinit var B: ViewStateButtonBinding + + 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) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_state_button, this) + B = ViewStateButtonBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StateButton) + val btnTxt = typedArray.getString(R.styleable.StateButton_btnStateText) + val btnIcon = typedArray.getResourceId( + R.styleable.StateButton_btnStateIcon, + R.drawable.ic_arrow_right + ) + + B.btn.text = btnTxt + B.btn.icon = ContextCompat.getDrawable(context, btnIcon) + typedArray.recycle() + } + + fun updateProgress(isVisible: Boolean) { + if (isVisible) + B.progress.visibility = View.VISIBLE + else + + B.progress.visibility = View.INVISIBLE + } + + fun addOnClickListener(onClickListener: OnClickListener) { + B.btn.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt new file mode 100644 index 000000000..ca54b2ca6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt @@ -0,0 +1,69 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom.layouts + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionHeaderBinding + +class ActionHeaderLayout : RelativeLayout { + + private lateinit var B: ViewActionHeaderBinding + + 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_header, this) + B = ViewActionHeaderBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionHeaderLayout) + val textPrimary = typedArray.getString(R.styleable.ActionHeaderLayout_headerTitle) + + textPrimary?.let { + B.txtTitle.text = it + } + } + + fun setHeader(header: String?) { + B.txtTitle.text = header + } + + fun addClickListener(onclickListener: OnClickListener?) { + B.imgAction.visibility = View.VISIBLE + B.imgAction.setOnClickListener(onclickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt new file mode 100644 index 000000000..abccd9ac4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt @@ -0,0 +1,83 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom.layouts + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewDevInfoBinding + +class ViewDevInfo : RelativeLayout { + + private lateinit var B: ViewDevInfoBinding + + 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) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_dev_info, this) + B = ViewDevInfoBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DevInfoLayout) + val icon = typedArray.getResourceId( + R.styleable.DevInfoLayout_imgIcon, + R.drawable.ic_map_marker + ) + + val textPrimary = typedArray.getString(R.styleable.DevInfoLayout_txtTitle) + val textSecondary = typedArray.getString(R.styleable.DevInfoLayout_txtSubtitle) + + B.img.setImageResource(icon) + B.txtTitle.text = textPrimary + B.txtSubtitle.text = textSecondary + typedArray.recycle() + } + + fun setTxtSubtitle(text: String?) { + B.txtSubtitle.text = text + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt b/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt new file mode 100644 index 000000000..75eb16752 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.ListPreference + +class AuroraListPreference : ListPreference { + + constructor(context: Context) : super(context) { + + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + + } + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + + } + + override fun getPersistedString(defaultReturnValue: String?): String? { + return getPersistedInt(-1).toString() + } + + override fun persistString(value: String?): Boolean { + return persistInt(Integer.valueOf(value)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt b/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt new file mode 100644 index 000000000..a4ed6520a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.custom.recycler + +import android.view.View +import androidx.recyclerview.widget.OrientationHelper +import androidx.recyclerview.widget.RecyclerView + +abstract class EndlessRecyclerOnScrollListener : RecyclerView.OnScrollListener { + + private var enabled = true + private var previousTotal = 0 + private var isLoading = true + private var visibleThreshold = RecyclerView.NO_POSITION + + var firstVisibleItem: Int = 0 + private set + var visibleItemCount: Int = 0 + private set + var totalItemCount: Int = 0 + private set + + private var isOrientationHelperVertical: Boolean = false + private var orientationHelper: OrientationHelper? = null + + var currentPage = 0 + private set + + lateinit var layoutManager: RecyclerView.LayoutManager + private set + + constructor() + + constructor(layoutManager: RecyclerView.LayoutManager) { + this.layoutManager = layoutManager + } + + constructor(visibleThreshold: Int) { + this.visibleThreshold = visibleThreshold + } + + constructor(layoutManager: RecyclerView.LayoutManager, visibleThreshold: Int) { + this.layoutManager = layoutManager + this.visibleThreshold = visibleThreshold + } + + private fun findFirstVisibleItemPosition(recyclerView: RecyclerView): Int { + val child = findOneVisibleChild(0, layoutManager.childCount, false, true) + return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition( + child + ) + } + + private fun findLastVisibleItemPosition(recyclerView: RecyclerView): Int { + val child = findOneVisibleChild(recyclerView.childCount - 1, -1, false, true) + return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition( + child + ) + } + + private fun findOneVisibleChild( + fromIndex: Int, + toIndex: Int, + completelyVisible: Boolean, + acceptPartiallyVisible: Boolean + ): View? { + if (layoutManager.canScrollVertically() != isOrientationHelperVertical || orientationHelper == null) { + isOrientationHelperVertical = layoutManager.canScrollVertically() + orientationHelper = if (isOrientationHelperVertical) + OrientationHelper.createVerticalHelper(layoutManager) + else + OrientationHelper.createHorizontalHelper(layoutManager) + } + + val mOrientationHelper = this.orientationHelper ?: return null + + val start = mOrientationHelper.startAfterPadding + val end = mOrientationHelper.endAfterPadding + val next = if (toIndex > fromIndex) 1 else -1 + var partiallyVisible: View? = null + var i = fromIndex + while (i != toIndex) { + val child = layoutManager.getChildAt(i) + if (child != null) { + val childStart = mOrientationHelper.getDecoratedStart(child) + val childEnd = mOrientationHelper.getDecoratedEnd(child) + if (childStart < end && childEnd > start) { + if (completelyVisible) { + if (childStart >= start && childEnd <= end) { + return child + } else if (acceptPartiallyVisible && partiallyVisible == null) { + partiallyVisible = child + } + } else { + return child + } + } + } + i += next + } + return partiallyVisible + } + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (enabled) { + if (!::layoutManager.isInitialized) { + layoutManager = recyclerView.layoutManager + ?: throw RuntimeException("A LayoutManager is required") + } + + + if (visibleThreshold == RecyclerView.NO_POSITION) { + visibleThreshold = + findLastVisibleItemPosition(recyclerView) - findFirstVisibleItemPosition( + recyclerView + ) + } + + visibleItemCount = recyclerView.childCount + totalItemCount = layoutManager.itemCount + firstVisibleItem = findFirstVisibleItemPosition(recyclerView) + + if (isLoading) { + if (totalItemCount > previousTotal) { + isLoading = false + previousTotal = totalItemCount + } + } + + if (!isLoading && totalItemCount - visibleItemCount <= firstVisibleItem + visibleThreshold) { + currentPage++ + onLoadMore(currentPage) + isLoading = true + } + } + } + + fun enable(): EndlessRecyclerOnScrollListener { + enabled = true + return this + } + + fun disable(): EndlessRecyclerOnScrollListener { + enabled = false + return this + } + + @JvmOverloads + fun resetPageCount(page: Int = 0) { + previousTotal = 0 + isLoading = true + currentPage = page + onLoadMore(currentPage) + } + + abstract fun onLoadMore(currentPage: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt new file mode 100644 index 000000000..87f8c4bb5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt @@ -0,0 +1,29 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.aurora.gplayapi.data.models.StreamCluster + +class CategoryCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) { + + override fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt new file mode 100644 index 000000000..cf93bccf8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.aurora.gplayapi.data.models.StreamCluster + +class EarlyAccessCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) { + + override fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster + && streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt new file mode 100644 index 000000000..cf6252840 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt @@ -0,0 +1,50 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.airbnb.epoxy.TypedEpoxyController +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.view.epoxy.groups.EditorChoiceModelGroup +import com.aurora.store.view.epoxy.views.EditorHeadViewModel_ + +class EditorChoiceController(private val callbacks: Callbacks) : + TypedEpoxyController>() { + + interface Callbacks { + fun onClick(editorChoiceCluster: EditorChoiceCluster) + } + + override fun buildModels(editorChoiceBundles: List) { + editorChoiceBundles.forEach { editorChoiceBundle -> + val idPrefix = editorChoiceBundle.id + + add( + EditorHeadViewModel_() + .id("header_${idPrefix}") + .title(editorChoiceBundle.bundleTitle) + ) + + editorChoiceBundle.bundleChoiceClusters.forEach { + add(EditorChoiceModelGroup(it, callbacks)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt new file mode 100644 index 000000000..5669bdd83 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt @@ -0,0 +1,47 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.gara.store.view.epoxy.controller + +import android.content.Context +import android.util.AttributeSet +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.google.android.flexbox.FlexboxLayoutManager + +class FlexLayoutManager : FlexboxLayoutManager { + constructor(context: Context?) : super(context) + constructor(context: Context?, flexDirection: Int) : super(context, flexDirection) + constructor(context: Context?, flexDirection: Int, flexWrap: Int) : super( + context, + flexDirection, + flexWrap + ) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams { + return LayoutParams(lp) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt new file mode 100644 index 000000000..5a25b5e58 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.airbnb.epoxy.TypedEpoxyController +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.view.epoxy.groups.CarouselModelGroup +import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ + +open class GenericCarouselController(private val callbacks: Callbacks) : + + TypedEpoxyController() { + + interface Callbacks { + fun onHeaderClicked(streamCluster: StreamCluster) + fun onClusterScrolled(streamCluster: StreamCluster) + fun onAppClick(app: App) + fun onAppLongClick(app: App) + } + + open fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster + && streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + && streamBundle.clusterAppList.count() > 1 //Filter clusters with single apps (mostly promotions) + } + + override fun buildModels(streamBundle: StreamBundle?) { + setFilterDuplicates(true) + if (streamBundle == null) { + for (i in 1..2) { + add( + CarouselShimmerGroup() + .id(i) + ) + } + } else { + if (streamBundle.streamClusters.isEmpty()) { + add( + NoAppViewModel_() + .id("no_app") + .icon(R.drawable.ic_apps) + .message("No apps available") + ) + } else { + if (streamBundle.streamClusters.size == 1) { + streamBundle + .streamClusters + .values + .filter { applyFilter(it) } + .forEach { streamCluster -> + streamCluster.clusterAppList.forEach { + add( + AppListViewModel_() + .id(it.id) + .app(it) + .click { _ -> callbacks.onAppClick(it) } + ) + } + } + + } else { + streamBundle + .streamClusters + .values + .filter { applyFilter(it) } + .forEach { streamCluster -> + add(CarouselModelGroup(streamCluster, callbacks)) + } + + } + if (streamBundle.hasNext()) + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt new file mode 100644 index 000000000..bd77894a2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.groups + +import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.Carousel +import com.airbnb.epoxy.ModelView + +@ModelView(saveViewState = true, autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) +class CarouselHorizontal(context: Context?) : Carousel(context) { + + override fun createLayoutManager(): LayoutManager { + return LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + } + + override fun getSnapHelperFactory(): Nothing? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt new file mode 100644 index 000000000..697048f2a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.AppViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ + +class CarouselModelGroup( + streamCluster: StreamCluster, + callbacks: GenericCarouselController.Callbacks +) : + EpoxyModelGroup( + R.layout.model_carousel_group, buildModels( + streamCluster, + callbacks + ) + ) { + companion object { + private fun buildModels( + streamCluster: StreamCluster, + callbacks: GenericCarouselController.Callbacks + ): List> { + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + + val idPrefix = streamCluster.id + + models.add( + HeaderViewModel_() + .id("${idPrefix}_header") + .title(streamCluster.clusterTitle) + .browseUrl(streamCluster.clusterBrowseUrl) + .click { _ -> + callbacks.onHeaderClicked(streamCluster) + } + ) + + for (app in streamCluster.clusterAppList) { + clusterViewModels.add( + AppViewModel_() + .id(app.id) + .app(app) + .click { _ -> + callbacks.onAppClick(app) + } + .longClick { _ -> + callbacks.onAppLongClick(app) + false + } + .onBind { _, _, position -> + val itemCount = clusterViewModels.count() + if (itemCount >= 2) { + if (position == clusterViewModels.count() - 2) { + callbacks.onClusterScrolled(streamCluster) + Log.i("Cluster %s Scrolled", streamCluster.clusterTitle) + } + } + } + ) + } + + if (streamCluster.hasNext()) { + clusterViewModels.add( + AppProgressViewModel_() + .id("${idPrefix}_progress") + ) + } + + models.add( + CarouselHorizontalModel_() + .id("${idPrefix}_cluster") + .models(clusterViewModels) + ) + + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt new file mode 100644 index 000000000..01e12bf03 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.store.R +import com.aurora.store.view.epoxy.views.shimmer.AppViewShimmerModel_ +import com.aurora.store.view.epoxy.views.shimmer.HeaderViewShimmerModel_ +import java.util.* +import kotlin.collections.ArrayList + +class CarouselShimmerGroup() : + EpoxyModelGroup( + R.layout.model_carousel_group, buildModels() + ) { + companion object { + private fun buildModels(): List> { + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + val idPrefix = UUID.randomUUID() + + for (i in 1..8) { + clusterViewModels.add( + AppViewShimmerModel_() + .id(i) + ) + } + + models.add( + HeaderViewShimmerModel_() + .id("shimmer_header") + ) + + models.add( + CarouselHorizontalModel_() + .id("cluster_${idPrefix}") + .models(clusterViewModels) + ) + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt new file mode 100644 index 000000000..2db7ba125 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.R +import com.aurora.store.view.epoxy.controller.EditorChoiceController +import com.aurora.store.view.epoxy.views.EditorImageViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ + +class EditorChoiceModelGroup( + editorChoiceCluster: EditorChoiceCluster, + callbacks: EditorChoiceController.Callbacks +) : + EpoxyModelGroup( + R.layout.model_editorchoice_group, buildModels( + editorChoiceCluster, + callbacks + ) + ) { + companion object { + private fun buildModels( + editorChoiceCluster: EditorChoiceCluster, + callbacks: EditorChoiceController.Callbacks + ): List> { + + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + + val idPrefix = editorChoiceCluster.id + + models.add( + HeaderViewModel_() + .id("header_${idPrefix}") + .title(editorChoiceCluster.clusterTitle) + .browseUrl(editorChoiceCluster.clusterBrowseUrl) + .click { _ -> callbacks.onClick(editorChoiceCluster) } + ) + + editorChoiceCluster.clusterArtwork.forEach { + clusterViewModels.add( + EditorImageViewModel_() + .id("artwork_${idPrefix}") + .artwork(it) + ) + } + + models.add( + CarouselHorizontalModel_() + .id("cluster_${idPrefix}") + .models(clusterViewModels) + ) + + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt new file mode 100644 index 000000000..6202c8919 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.view.isVisible +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.Accent +import com.aurora.store.databinding.ViewAccentBinding + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AccentView : RelativeLayout { + + private lateinit var B: ViewAccentBinding + + 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_accent, this) + B = ViewAccentBinding.bind(view) + } + + @ModelProp + fun accent(accent: Accent) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + B.img.backgroundTintList = ColorStateList.valueOf(Color.parseColor(accent.accent)) + } else { + B.img.setBackgroundColor(Color.parseColor(accent.accent)) + } + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.tick.isVisible = isChecked + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt new file mode 100644 index 000000000..dcbf64d0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt @@ -0,0 +1,54 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppProgressBinding + +@ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT) +class AppProgressView : RelativeLayout { + + private lateinit var B: ViewAppProgressBinding + + 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_app_progress, this) + B = ViewAppProgressBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt new file mode 100644 index 000000000..9f16de7b8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt @@ -0,0 +1,49 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.view.View +import android.view.animation.AnimationUtils +import com.airbnb.epoxy.EpoxyModel + +abstract class BaseView : EpoxyModel() { + + override fun bind(view: T) { + super.bind(view) + when (view) { + is AppListView -> { + view.startAnimation( + AnimationUtils.loadAnimation( + view.context, + android.R.anim.fade_in + ) + ) + } + } + } + + override fun unbind(view: T) { + when (view) { + is AppListView -> { + view.clearAnimation() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt new file mode 100644 index 000000000..2cac41662 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.data.model.Black +import com.aurora.store.databinding.ViewBlackBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class BlackView : RelativeLayout { + + private lateinit var B: ViewBlackBinding + + 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_black, this) + B = ViewBlackBinding.bind(view) + } + + @ModelProp + fun black(black: Black) { + B.imgIcon.load(black.drawable) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine1.text = black.displayName + B.txtLine2.text = black.packageName + B.txtLine3.text = ("${black.versionName}.${black.versionCode}") + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt new file mode 100644 index 000000000..7f220207b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.R +import com.aurora.store.databinding.ViewCategoryBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class CategoryView : RelativeLayout { + + private lateinit var B: ViewCategoryBinding + + 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_category, this) + B = ViewCategoryBinding.bind(view) + } + + @ModelProp + fun category(category: Category) { + B.txtName.text = category.title + B.imgBackground.load(category.imageUrl, DrawableTransitionOptions.withCrossFade()) { + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgBackground.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt new file mode 100644 index 000000000..8ed0476d9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt @@ -0,0 +1,146 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.model.DownloadFile +import com.aurora.store.databinding.ViewDownloadBinding +import com.aurora.store.util.CommonUtil.getDownloadSpeedString +import com.aurora.store.util.CommonUtil.getETAString +import com.aurora.store.util.CommonUtil.humanReadableByteValue +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.Status +import java.lang.reflect.Modifier +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DownloadView : RelativeLayout { + + private lateinit var B: ViewDownloadBinding + + private val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + + 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_download, this) + B = ViewDownloadBinding.bind(view) + } + + @ModelProp + fun download(downloadFile: DownloadFile) { + val download = downloadFile.download + val extras = download.extras.getString(Constants.STRING_EXTRA, "{}") + val app = gson.fromJson(extras, App::class.java) + + app?.let { + B.imgDownload.load(app.iconArtwork.url, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + B.txtTitle.text = app.displayName + } + + B.txtStatus.text = download.status.name + .toLowerCase(Locale.getDefault()) + .capitalize(Locale.getDefault()) + + B.txtSize.text = StringBuilder() + .append(humanReadableByteValue(download.downloaded, true)) + .append("/") + .append(humanReadableByteValue(download.total, true)) + + val file = download.file + B.txtFile.text = file.substring(file.lastIndexOf("/") + 1) + + var progress = download.progress + if (progress == -1) { + progress = 0 + } + + B.progressDownload.progress = progress + B.txtProgress.text = ("$progress%") + + B.txtEta.text = getETAString(context, download.etaInMilliSeconds) + B.txtSpeed.text = getDownloadSpeedString( + context, + download.downloadedBytesPerSecond + ) + + when (download.status) { + Status.DOWNLOADING, Status.QUEUED, Status.ADDED -> { + B.txtSpeed.visibility = VISIBLE + B.txtEta.visibility = VISIBLE + } + else -> { + B.txtSpeed.visibility = INVISIBLE + B.txtEta.visibility = INVISIBLE + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgDownload.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt new file mode 100644 index 000000000..a1d30ce61 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt @@ -0,0 +1,63 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewEditorHeadBinding + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class EditorHeadView : RelativeLayout { + + private lateinit var B: ViewEditorHeadBinding + + 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_editor_head, this) + B = ViewEditorHeadBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.title.text = title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt new file mode 100644 index 000000000..a18738179 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.R +import com.aurora.store.databinding.ViewEditorImageBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class EditorImageView : RelativeLayout { + + private lateinit var B: ViewEditorImageBinding + + 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_editor_image, this) + B = ViewEditorImageBinding.bind(view) + } + + @ModelProp + fun artwork(artwork: Artwork) { + when (artwork.type) { + 14 -> { + B.img.layoutParams.width = + resources.getDimension(R.dimen.icon_size_large).toInt() * 2 + B.img.requestLayout() + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + transform(RoundedCorners(16)) + } + } + else -> { + B.img.layoutParams.width = resources.getDimension(R.dimen.icon_size_large).toInt() + B.img.requestLayout() + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + transform(RoundedCorners(16)) + } + } + } + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt new file mode 100644 index 000000000..51a46e85a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.Nullable +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class HeaderView : RelativeLayout { + + private lateinit var B: ViewHeaderBinding + + 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_header, this) + B = ViewHeaderBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @JvmOverloads + @ModelProp + fun browseUrl(@Nullable browseUrl: String = String()) { + if (browseUrl.isEmpty()) + B.imgAction.visibility = INVISIBLE + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgAction.visibility = VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt new file mode 100644 index 000000000..cc359b46c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionHeaderBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class MinimalHeaderView : RelativeLayout { + + private lateinit var B: ViewActionHeaderBinding + + 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_header, this) + B = ViewActionHeaderBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.imgAction.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgAction.visibility = VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt new file mode 100644 index 000000000..723eb3a0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt @@ -0,0 +1,98 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.store.R +import com.aurora.store.databinding.ViewSearchSuggestionBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class SearchSuggestionView : RelativeLayout { + + private lateinit var B: ViewSearchSuggestionBinding + + 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_search_suggestion, this) + B = ViewSearchSuggestionBinding.bind(view) + } + + @ModelProp + fun entry(searchSuggestEntry: SearchSuggestEntry) { + if (searchSuggestEntry.hasImageContainer()) { + B.img.load(searchSuggestEntry.imageContainer.imageUrl) { + transform(RoundedCorners(8)) + } + } else { + B.img.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.ic_search_suggestion + ) + ) + } + + B.txtTitle.text = searchSuggestEntry.title + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun action(onClickListener: OnClickListener?) { + B.action.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt new file mode 100644 index 000000000..e1aa2f4ee --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt @@ -0,0 +1,77 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +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.Theme +import com.aurora.store.databinding.ViewThemeBinding + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ThemeView : RelativeLayout { + + private lateinit var B: ViewThemeBinding + + 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_theme, this) + B = ViewThemeBinding.bind(view) + } + + @ModelProp + fun theme(theme: Theme) { + B.line1.text = theme.title + B.line2.text = theme.subtitle + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt new file mode 100644 index 000000000..4beaaf6f2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderUpdateBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class UpdateHeaderView : RelativeLayout { + + private lateinit var B: ViewHeaderUpdateBinding + + 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_header_update, this) + B = ViewHeaderUpdateBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @ModelProp + fun action(action: String) { + B.btnAction.text = action + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.btnAction.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.btnAction.isEnabled = true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt new file mode 100644 index 000000000..8f87fefc9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt @@ -0,0 +1,110 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppListBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.getString +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppListView : RelativeLayout { + + private lateinit var B: ViewAppListBinding + + 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_app_list, this) + B = ViewAppListBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine1.text = app.displayName + B.txtLine2.text = app.developerName + + val extras: MutableList = mutableListOf() + extras.add(CommonUtil.addSiPrefix(app.size)) + extras.add("${app.labeledRating}★") + extras.add( + if (app.isFree) + getString(R.string.details_free) + else + getString(R.string.details_paid) + ) + + if (app.containsAds) + extras.add(getString(R.string.details_contains_ads)) + + if (app.dependencies.dependentPackages.isNotEmpty()) + extras.add(getString(R.string.details_gsf_dependent)) + + B.txtLine3.text = extras.joinToString(separator = " • ") + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt new file mode 100644 index 000000000..17d411f54 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt @@ -0,0 +1,124 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import androidx.core.text.HtmlCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppUpdateBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppUpdateView : RelativeLayout { + + private lateinit var B: ViewAppUpdateBinding + private var expanded: Boolean = false + + 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_app_update, this) + B = ViewAppUpdateBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtLine1.text = app.displayName + B.imgIcon.load(app.iconArtwork.url, withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine2.text = app.developerName + B.txtLine3.text = CommonUtil.addSiPrefix(app.size) + B.txtChangelog.text = if (app.changes.isNotEmpty()) + HtmlCompat.fromHtml( + app.changes, + HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS + ) + else + context.getString(R.string.details_changelog_unavailable) + + B.headerIndicator.setOnClickListener { + B.expansionLayout.let { + if (it.isExpanded) { + it.collapse(true) + } else { + it.expand(true) + } + } + } + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.layoutContent.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.layoutContent.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + B.headerIndicator.removeCallbacks { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt new file mode 100644 index 000000000..45a49d1e2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt @@ -0,0 +1,93 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppView : RelativeLayout { + + private lateinit var B: ViewAppBinding + + 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_app, this) + B = ViewAppBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtName.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + if (app.size > 0) + B.txtSize.text = CommonUtil.addSiPrefix(app.size) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt new file mode 100644 index 000000000..1c7568e7e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt @@ -0,0 +1,65 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.app + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewNoAppAltBinding +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class NoAppAltView : RelativeLayout { + + private lateinit var B: ViewNoAppAltBinding + + 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_no_app_alt, this) + B = ViewNoAppAltBinding.bind(view) + } + + @ModelProp + fun message(message: String) { + B.txtMsg.text = message + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt new file mode 100644 index 000000000..c03f262e6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt @@ -0,0 +1,73 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.app + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewNoAppBinding +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT, + baseModelClass = BaseView::class +) +class NoAppView : RelativeLayout { + + private lateinit var B: ViewNoAppBinding + + 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_no_app, this) + B = ViewNoAppBinding.bind(view) + } + + @ModelProp + fun message(message: String) { + B.txt.text = message + } + + @ModelProp + fun icon(icon: Int?) { + icon?.let { + B.img.setImageDrawable(ContextCompat.getDrawable(context, icon)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt new file mode 100644 index 000000000..49306da25 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt @@ -0,0 +1,89 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.details + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppDependentBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.BaseView +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppDependentView : RelativeLayout { + + private lateinit var B: ViewAppDependentBinding + + 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_app_dependent, this) + B = ViewAppDependentBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtName.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt new file mode 100644 index 000000000..f6cb92f4b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt @@ -0,0 +1,91 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.details.Badge +import com.aurora.store.R +import com.aurora.store.databinding.ViewBadgeBinding +import com.aurora.store.util.extensions.load + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class BadgeView : RelativeLayout { + + private lateinit var B: ViewBadgeBinding + + 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_badge, this) + B = ViewBadgeBinding.bind(view) + } + + @ModelProp + fun badge(badge: Badge) { + if (badge.textMajor.isEmpty()) { + if (badge.textMinor.isEmpty()) { + B.txt.text = badge.textDescription + } else { + B.txt.text = badge.textMinor + } + } else { + B.txt.text = badge.textMajor + } + + badge.artwork?.let { + B.img.load(it.url) { + + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt new file mode 100644 index 000000000..d876675eb --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.data.model.ExodusTracker +import com.aurora.store.databinding.ViewExodusBinding + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ExodusView : RelativeLayout { + + private lateinit var B: ViewExodusBinding + + 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_exodus, this) + B = ViewExodusBinding.bind(view) + } + + @ModelProp + fun tracker(report: ExodusTracker) { + B.txtTitle.text = report.name + B.txtSubtitle.text = report.signature + B.txtDescription.text = report.date + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt new file mode 100644 index 000000000..2af3c2edf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.gplayapi.data.models.File +import com.aurora.store.R +import com.aurora.store.databinding.ViewFileBinding +import com.aurora.store.util.CommonUtil + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class FileView : RelativeLayout { + + private lateinit var B: ViewFileBinding + + 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_file, this) + B = ViewFileBinding.bind(view) + } + + @ModelProp + fun file(file: File) { + B.line1.text = file.name + B.line2.text = CommonUtil.addSiPrefix(file.size) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt new file mode 100644 index 000000000..096404ea3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt @@ -0,0 +1,118 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.GlideApp +import com.aurora.store.R +import com.aurora.store.databinding.ViewScreenshotLargeBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.px +import com.aurora.store.util.extensions.runOnUiThread +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT, + baseModelClass = BaseView::class +) +class LargeScreenshotView : RelativeLayout { + + private lateinit var B: ViewScreenshotLargeBinding + + 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_screenshot_large, this) + B = ViewScreenshotLargeBinding.bind(view) + } + + @ModelProp + fun artwork(artwork: Artwork) { + GlideApp.with(context) + .load(artwork.url) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target, + isFirstResource: Boolean + ): Boolean { + return false + } + + override fun onResourceReady( + drawable: Drawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + runOnUiThread { + if (artwork.height != 0 && artwork.width != 0) { + B.img.layoutParams.height = artwork.height.px.toInt() + B.img.layoutParams.width = artwork.width.px.toInt() + } else { + val displayMetrics = Resources.getSystem().displayMetrics + val height = displayMetrics.heightPixels + val width = displayMetrics.widthPixels + B.img.layoutParams.width = width + B.img.layoutParams.height = height + } + B.img.setImageDrawable(drawable) + B.img.requestLayout() + } + return false + } + }).submit() + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt new file mode 100644 index 000000000..aaae0a9d5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.details + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.text.HtmlCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.details.Badge +import com.aurora.store.R +import com.aurora.store.databinding.ViewMoreBadgeBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class MoreBadgeView : RelativeLayout { + + private lateinit var B: ViewMoreBadgeBinding + + 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_more_badge, this) + B = ViewMoreBadgeBinding.bind(view) + } + + @ModelProp + fun badge(badge: Badge) { + B.line1.text = badge.textMajor + + badge.textMinorHtml?.let { + if (it.isNotEmpty()) { + B.line2.text = HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT) + } else { + B.line2.text = badge.textMinor + } + } + + badge.textDescription?.let { + if (it.isNotEmpty()) { + B.line2.text = it + } + } + + badge.artwork?.let { + B.img.load(it.url) { + placeholder(R.drawable.ic_arrow_right) + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt new file mode 100644 index 000000000..5dd2cdeb0 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt @@ -0,0 +1,98 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Review +import com.aurora.store.R +import com.aurora.store.databinding.ViewReviewBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.toDate +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ReviewView : RelativeLayout { + + private lateinit var B: ViewReviewBinding + + 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_review, this) + B = ViewReviewBinding.bind(view) + } + + @ModelProp + fun review(review: Review) { + B.txtAuthor.text = review.userName + B.txtTime.text = review.timeStamp.toDate() + B.txtComment.text = review.comment + + B.img.load(review.userPhotoUrl, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + B.rating.rating = review.rating.toFloat() + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.line2.justificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD + }*/ + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt new file mode 100644 index 000000000..9ea8fa281 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.R +import com.aurora.store.databinding.ViewScreenshotBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.px +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ScreenshotView : RelativeLayout { + + private lateinit var B: ViewScreenshotBinding + + private var position: Int = 0 + + interface ScreenshotCallback { + fun onClick(position: Int = 0) + } + + 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_screenshot, this) + B = ViewScreenshotBinding.bind(view) + } + + @ModelProp + fun position(pos: Int) { + position = pos + } + + @ModelProp + fun artwork(artwork: Artwork) { + normalizeSize(artwork) + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_rounded) + transform(RoundedCorners(8.px.toInt())) + } + } + + private fun normalizeSize(artwork: Artwork) { + if (artwork.height != 0 && artwork.width != 0) { + + val viewHeight = artwork.height + val viewWidth = artwork.width + + var normalizedHeight: Int = viewHeight + var normalizedWidth: Int = viewWidth + + + if (viewHeight == viewWidth) { + normalizedHeight = 240 + normalizedWidth = 240 + } else if (viewHeight > viewWidth) { + normalizedHeight = 240 + normalizedWidth = 135 + } else if (viewHeight < viewWidth) { + normalizedHeight = 240 + normalizedWidth = 427 + } + + B.img.layoutParams.height = normalizedHeight.px.toInt() + B.img.layoutParams.width = normalizedWidth.px.toInt() + B.img.requestLayout() + } + } + + @CallbackProp + fun callback(screenshotCallback: ScreenshotCallback?) { + B.img.setOnClickListener { + screenshotCallback?.onClick(position) + } + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt new file mode 100644 index 000000000..f5421d99b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +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.Dash +import com.aurora.store.databinding.ViewDashBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.aurora.store.view.ui.onboarding.WelcomeFragment + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DashView : RelativeLayout { + + private lateinit var B: ViewDashBinding + + 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_dash, this) + B = ViewDashBinding.bind(view) + } + + @ModelProp + fun dash(dash: Dash) { + B.line1.text = dash.title + B.line2.text = dash.subtitle + + var icon = WelcomeFragment.icMap[dash.icon] + if (icon == null) + icon = R.drawable.ic_arrow_right + B.img.setImageDrawable( + ContextCompat.getDrawable( + context, + icon + ) + ) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt new file mode 100644 index 000000000..cbbf9b6c7 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewDeviceBinding +import com.aurora.store.view.epoxy.views.BaseView +import org.apache.commons.lang3.StringUtils +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DeviceView : RelativeLayout { + + private lateinit var B: ViewDeviceBinding + + 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_device, this) + B = ViewDeviceBinding.bind(view) + } + + @ModelProp + fun properties(properties: Properties) { + B.line1.text = properties.getProperty("UserReadableName") + B.line2.text = StringUtils.joinWith( + " \u2022 ", + properties.getProperty("Build.MANUFACTURER"), + "API " + properties.getProperty("Build.VERSION.SDK_INT") + ) + B.line3.text = properties.getProperty("Platforms") + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt new file mode 100644 index 000000000..e117e8faf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt @@ -0,0 +1,79 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +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.Installer +import com.aurora.store.databinding.ViewInstallerBinding +import com.aurora.store.view.epoxy.views.BaseView + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class InstallerView : RelativeLayout { + + private lateinit var B: ViewInstallerBinding + + 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_installer, this) + B = ViewInstallerBinding.bind(view) + } + + @ModelProp + fun installer(installer: Installer) { + B.line1.text = installer.title + B.line2.text = installer.subtitle + B.line3.text = installer.description + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt new file mode 100644 index 000000000..82e10949d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +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.Link +import com.aurora.store.databinding.ViewLinkBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.show +import com.aurora.store.view.epoxy.views.BaseView + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class LinkView : RelativeLayout { + + private lateinit var B: ViewLinkBinding + + 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_link, this) + B = ViewLinkBinding.bind(view) + } + + @ModelProp + fun link(link: Link) { + B.line1.text = link.title + B.line2.text = link.subtitle + if (!link.url.startsWith("http")) { + B.line3.show() + B.line3.text = link.url + } else { + B.line3.show() + } + B.imgIcon.load(link.icon) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt new file mode 100644 index 000000000..03edd4ed5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewLocaleBinding +import com.aurora.store.view.epoxy.views.BaseView +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class LocaleView : RelativeLayout { + + private lateinit var B: ViewLocaleBinding + + 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_locale, this) + B = ViewLocaleBinding.bind(view) + } + + @ModelProp + fun locale(locale: Locale) { + B.line1.text = locale.displayName + B.line2.text = if (locale.displayCountry.isNotEmpty()) + locale.displayCountry + else + locale.displayLanguage + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt new file mode 100644 index 000000000..79e62dd24 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppListShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppListViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewAppListShimmerBinding + + 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_app_list_shimmer, this) + B = ViewAppListShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt new file mode 100644 index 000000000..c651c443d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewAppShimmerBinding + + 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_app_shimmer, this) + B = ViewAppShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt new file mode 100644 index 000000000..a10f74539 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class HeaderViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewHeaderShimmerBinding + + 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_header_shimmer, this) + B = ViewHeaderShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt b/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt new file mode 100644 index 000000000..e0ac61f97 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt @@ -0,0 +1,128 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.about + +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.aurora.store.BuildConfig +import com.aurora.store.R +import com.aurora.store.data.model.Link +import com.aurora.store.databinding.ActivityAboutBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.copyToClipBoard +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.preference.LinkViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity + +class AboutActivity : BaseActivity() { + + private lateinit var B: ActivityAboutBinding + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityAboutBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + attachAppDetails() + attachRecycler() + + updateController() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_about) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachAppDetails() { + B.imgIcon.load(R.drawable.ic_logo) + B.line2.text = ("v${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}") + } + + private fun attachRecycler() { + with(B.epoxyRecycler) { + layoutManager = LinearLayoutManager( + this@AboutActivity, + RecyclerView.VERTICAL, + false + ) + } + } + + private fun updateController() { + val linkURLS = resources.getStringArray(R.array.link_urls) + val linkTitles = resources.getStringArray(R.array.link_titles) + val linkSummary = resources.getStringArray(R.array.link_subtitle) + + val linkIcons = intArrayOf( + R.drawable.ic_bitcoin_btc, + R.drawable.ic_bitcoin_bch, + R.drawable.ic_ethereum_eth, + R.drawable.ic_bhim, + R.drawable.ic_paypal, + R.drawable.ic_libera_pay, + R.drawable.ic_gitlab, + R.drawable.ic_xda, + R.drawable.ic_telegram, + R.drawable.ic_fdroid + ) + + B.epoxyRecycler.withModels { + for (i in linkURLS.indices) { + val link = Link( + id = i, + title = linkTitles[i], + subtitle = linkSummary[i], + url = linkURLS[i], + icon = linkIcons[i] + ) + add( + LinkViewModel_() + .id(i) + .link(link) + .click { _ -> processUrl(link.url) } + ) + } + } + } + + private fun processUrl(url: String) { + if (url.startsWith("http")) + browse(url) + else + copyToClipBoard(url) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt b/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt new file mode 100644 index 000000000..d55ff92bd --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt @@ -0,0 +1,246 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.account + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.data.AuthState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.providers.AccountProvider +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityAccountBinding +import com.aurora.store.util.CommonUtil.getEmptyActivityBundle +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.load +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.auth.AuthViewModel +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe + +class AccountActivity : BaseActivity() { + + private lateinit var VM: AuthViewModel + private lateinit var B: ActivityAccountBinding + + private lateinit var authData: AuthData + private lateinit var accountProvider: AccountProvider + + private val URL_TOS = "https://www.google.com/mobile/android/market-tos.html" + private val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/LICENSE" + private val URL_DISCLAIMER = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/DISCLAIMER" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + EventBus.getDefault().register(this); + + B = ActivityAccountBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AuthViewModel::class.java) + + setContentView(B.root) + + authData = AuthProvider.with(this).getAuthData() + accountProvider = AccountProvider.with(this) + + attachToolbar() + attachChips() + attachActions() + + updateContents() + + VM.liveData.observe(this, { + when (it) { + AuthState.Valid -> { + + } + + AuthState.Available -> { + updateStatus("Verifying session") + updateActionLayout(false) + } + + AuthState.Unavailable -> { + updateStatus("You need to login first") + updateActionLayout(true) + } + + AuthState.SignedIn -> { + updateContents() + } + + AuthState.SignedOut -> { + updateStatus("Last session scrapped") + updateActionLayout(true) + } + + is AuthState.Status -> { + updateStatus(it.status) + } + } + }) + } + + override fun onDestroy() { + EventBus.getDefault().unregister(this) + super.onDestroy() + } + + @Subscribe() + fun onEventReceived(event: BusEvent) { + when (event) { + is BusEvent.GoogleAAS -> { + if (event.success) { + updateStatus("Verifying Google Session") + VM.buildGoogleAuthData(event.email, event.aasToken) + } else { + updateStatus("Failed to login via Google") + } + } + else -> { + + } + } + } + + private fun updateContents() { + if (accountProvider.isSignedIn()) { + B.viewFlipper.displayedChild = 1 + updateStatus("Woah! all good.") + } else { + B.viewFlipper.displayedChild = 0 + updateStatus("Login and enjoy.") + } + + updateUserProfile() + } + + private fun updateStatus(string: String?) { + runOnUiThread { + B.txtStatus.apply { + text = string + } + } + } + + private fun updateActionLayout(isVisible: Boolean) { + if (isVisible) { + B.layoutAction.visibility = View.VISIBLE + } else { + B.layoutAction.visibility = View.INVISIBLE + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_account_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachChips() { + B.chipDisclaimer.setOnClickListener { browse(URL_DISCLAIMER) } + B.chipLicense.setOnClickListener { browse(URL_LICENSE) } + B.chipTos.setOnClickListener { browse(URL_TOS) } + } + + + private fun attachActions() { + B.btnAnonymous.updateProgress(false) + B.btnGoogle.updateProgress(false) + + B.btnAnonymous.addOnClickListener { + B.btnAnonymous.updateProgress(true) + VM.buildAnonymousAuthData() + } + + B.btnGoogle.addOnClickListener { + B.btnGoogle.updateProgress(true) + openGoogleActivity() + } + + B.btnLogout.addOnClickListener { + task { + AccountProvider.with(this).logout() + } successUi { + B.btnAnonymous.updateProgress(false) + B.btnGoogle.updateProgress(false) + updateContents() + } failUi { + + } + } + } + + private fun updateUserProfile() { + authData = AuthProvider.with(this).getAuthData() + + if (accountProvider.isSignedIn()) { + authData.userProfile?.let { + B.imgAvatar.load(it.artwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + B.txtName.text = if (authData.isAnonymous) + "Anonymous" + else + it.name + + B.txtEmail.text = it.email + } + } else { + B.imgAvatar.load(R.mipmap.ic_launcher) { + transform(RoundedCorners(32)) + } + B.txtName.text = getString(R.string.app_name) + B.txtEmail.text = getString(R.string.account_logged_out) + } + } + + private fun moveToContent() { + runOnUiThread { + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent, getEmptyActivityBundle(this)) + } + } + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt b/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt new file mode 100644 index 000000000..5f8913763 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt @@ -0,0 +1,141 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.account + +import android.annotation.SuppressLint +import android.os.Build +import android.os.Bundle +import android.webkit.CookieManager +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Toast +import com.aurora.Constants +import com.aurora.store.data.event.BusEvent +import com.aurora.store.databinding.ActivityGoogleBinding +import com.aurora.store.util.AC2DMTask +import com.aurora.store.util.Preferences +import com.aurora.store.util.Util +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import org.greenrobot.eventbus.EventBus + +class GoogleActivity : BaseActivity() { + + private lateinit var B: ActivityGoogleBinding + + private val cookieManager = CookieManager.getInstance() + + override fun onConnected() { + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + hideNetworkConnectivitySheet() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGoogleBinding.inflate(layoutInflater) + setContentView(B.root) + + setupWebView() + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebView() { + if (isLAndAbove()) { + cookieManager.removeAllCookies(null) + cookieManager.acceptThirdPartyCookies(B.webview) + cookieManager.setAcceptThirdPartyCookies(B.webview, true) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.webview.settings.safeBrowsingEnabled = false + } + + B.webview.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + val cookies = CookieManager.getInstance().getCookie(url) + val cookieMap = Util.parseCookieString(cookies) + if (cookieMap.isNotEmpty() && cookieMap[AUTH_TOKEN] != null) { + val oauthToken = cookieMap[AUTH_TOKEN] + B.webview.evaluateJavascript("(function() { return document.getElementById('profileIdentifier').innerHTML; })();") { + val email = it.replace("\"".toRegex(), "") + buildAuthData(email, oauthToken) + } + } + } + } + + B.webview.apply { + settings.apply { + allowContentAccess = true + databaseEnabled = true + domStorageEnabled = true + javaScriptEnabled = true + cacheMode = WebSettings.LOAD_DEFAULT + } + loadUrl(EMBEDDED_SETUP_URL) + } + } + + private fun buildAuthData(email: String, oauthToken: String?) { + task { + AC2DMTask().getAC2DMResponse(email, oauthToken) + } success { + if (it.isNotEmpty()) { + val aasToken = it["Token"] + if (aasToken != null) { + Preferences.putString(this, Constants.ACCOUNT_EMAIL_PLAIN, email) + Preferences.putString(this, Constants.ACCOUNT_AAS_PLAIN, aasToken) + EventBus.getDefault().post(BusEvent.GoogleAAS(true, email, aasToken)) + } else { + Preferences.putString(this, Constants.ACCOUNT_EMAIL_PLAIN, "") + Preferences.putString(this, Constants.ACCOUNT_AAS_PLAIN, "") + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + } + } else { + Toast.makeText(this, "Failed to generate AAS Token", Toast.LENGTH_LONG).show() + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + } + + //Close Activity + close() + } fail { + Toast.makeText(this, "Failed to generate AAS Token", Toast.LENGTH_LONG).show() + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + + //Close Activity + close() + } + } + + companion object { + const val EMBEDDED_SETUP_URL = + "https://accounts.google.com/EmbeddedSetup/identifier?flowName=EmbeddedSetupAndroid" + const val AUTH_TOKEN = "oauth_token" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt b/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt new file mode 100644 index 000000000..9d6fea394 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt @@ -0,0 +1,103 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityGenericPagerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + +class AppsGamesActivity : BaseActivity() { + + private lateinit var B: ActivityGenericPagerBinding + private lateinit var authData: AuthData + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericPagerBinding.inflate(layoutInflater) + authData = AuthProvider.with(this).getAuthData() + + setContentView(B.root) + attachToolbar() + attachViewPager() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_apps_games) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachViewPager() { + B.pager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = getString(R.string.title_installed) + 1 -> tab.text = getString(R.string.title_library) + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> InstalledAppsFragment.newInstance() + 1 -> LibraryAppsFragment.newInstance() + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 1 + else + 2 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt new file mode 100644 index 000000000..75ea2fc9c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.view.ui.sheets.AppMenuSheet +import com.aurora.store.viewmodel.all.InstalledViewModel + +class InstalledAppsFragment : BaseFragment() { + + private lateinit var VM: InstalledViewModel + private lateinit var B: FragmentUpdatesBinding + + companion object { + @JvmStatic + fun newInstance(): InstalledAppsFragment { + return InstalledAppsFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(InstalledViewModel::class.java) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + B.swipeRefreshLayout.isRefreshing = false + }) + + B.swipeRefreshLayout.setOnRefreshListener { + VM.observe() + } + + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + add( + HeaderViewModel_() + .id("header") + .title("${appList.size} apps installed") + ) + appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + .longClick { _ -> + openAppMenuSheet(app) + false + } + ) + } + } + } + } + + private fun openAppMenuSheet(app: App) { + val fragment = childFragmentManager.findFragmentByTag(AppMenuSheet.TAG) + if (fragment != null) + childFragmentManager.beginTransaction().remove(fragment) + + AppMenuSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + }.show( + childFragmentManager, + AppMenuSheet.TAG + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt new file mode 100644 index 000000000..0ebba9183 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.all.LibraryAppsViewModel + +class LibraryAppsFragment : BaseFragment() { + + private lateinit var VM: LibraryAppsViewModel + private lateinit var B: FragmentUpdatesBinding + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + companion object { + @JvmStatic + fun newInstance(): LibraryAppsFragment { + return LibraryAppsFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(LibraryAppsViewModel::class.java) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + B.swipeRefreshLayout.isEnabled = false + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + attachRecycler() + + updateController(null) + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + add( + HeaderViewModel_() + .id("header") + .title( + if (streamCluster.clusterTitle.isEmpty()) + getString(R.string.title_apps_library) + else + streamCluster.clusterTitle + ) + ) + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt new file mode 100644 index 000000000..36ec47819 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt @@ -0,0 +1,106 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentAppsGamesBinding +import com.aurora.store.view.ui.commons.CategoryFragment +import com.aurora.store.view.ui.commons.EditorChoiceFragment +import com.aurora.store.view.ui.commons.ForYouFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + + +class AppsContainerFragment : Fragment() { + + private lateinit var B: FragmentAppsGamesBinding + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentAppsGamesBinding.bind( + inflater.inflate( + R.layout.fragment_apps_games, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false //Disable viewpager scroll to avoid scroll conflicts + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = "For you" + 1 -> tab.text = "Top charts" + 2 -> tab.text = "Categories" + 3 -> tab.text = "Editor's choice" + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> ForYouFragment.newInstance(0) + 1 -> TopChartContainerFragment() + 2 -> CategoryFragment.newInstance(0) + 3 -> EditorChoiceFragment.newInstance(0) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 3 + else + 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt new file mode 100644 index 000000000..3140d9a19 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentTopChartBinding + + +class TopChartContainerFragment : Fragment() { + + private lateinit var B: FragmentTopChartBinding + + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentTopChartBinding.bind( + inflater.inflate( + R.layout.fragment_top_chart, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle) + B.topTabGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.tab_top_free -> B.pager.setCurrentItem(0, true) + R.id.tab_top_grossing -> B.pager.setCurrentItem(1, true) + R.id.tab_trending -> B.pager.setCurrentItem(2, true) + R.id.tab_top_paid -> B.pager.setCurrentItem(3, true) + } + } + + B.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> B.topTabGroup.check(R.id.tab_top_free) + 1 -> B.topTabGroup.check(R.id.tab_top_grossing) + 2 -> B.topTabGroup.check(R.id.tab_trending) + 3 -> B.topTabGroup.check(R.id.tab_top_paid) + } + } + }) + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> TopChartFragment.newInstance(0, 0) + 1 -> TopChartFragment.newInstance(0, 1) + 2 -> TopChartFragment.newInstance(0, 2) + 3 -> TopChartFragment.newInstance(0, 3) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt new file mode 100644 index 000000000..9ff6b1c13 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt @@ -0,0 +1,132 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentTopContainerBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.topchart.* + +class TopChartFragment : BaseFragment() { + + private lateinit var VM: BaseChartViewModel + private lateinit var B: FragmentTopContainerBinding + + private var chartType = 0 + private var chartCategory = 0 + + companion object { + @JvmStatic + fun newInstance(chartType: Int, chartCategory: Int): TopChartFragment { + return TopChartFragment().apply { + arguments = Bundle().apply { + putInt(Constants.TOP_CHART_TYPE, chartType) + putInt(Constants.TOP_CHART_CATEGORY, chartCategory) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentTopContainerBinding.bind( + inflater.inflate( + R.layout.fragment_top_container, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) + chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) + } + + when (chartCategory) { + 0 -> VM = ViewModelProvider(this).get(TopFreeAppChartViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(TopGrossingAppChartViewModel::class.java) + 2 -> VM = ViewModelProvider(this).get(TrendingAppChartViewModel::class.java) + 3 -> VM = ViewModelProvider(this).get(TopPaidAppChartViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + }) + + updateController(null) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt new file mode 100644 index 000000000..f74cb3fc6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt @@ -0,0 +1,144 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.view.ui.account.GoogleActivity +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.details.DetailsMoreActivity +import com.aurora.store.view.ui.details.DetailsReviewActivity +import com.aurora.store.view.ui.sheets.NetworkDialogSheet +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + + +abstract class BaseActivity : AppCompatActivity(), NetworkProvider.NetworkListener { + + protected val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + override fun onCreate(savedInstanceState: Bundle?) { + val themeId = Preferences.getInteger(this, PREFERENCE_THEME_TYPE) + val accentId = Preferences.getInteger(this, PREFERENCE_THEME_ACCENT) + applyTheme(themeId, accentId) + super.onCreate(savedInstanceState) + } + + fun openDetailsActivity(app: App) { + val intent = Intent( + this, + AppDetailsActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openDetailsMoreActivity(app: App) { + val intent = Intent( + this, + DetailsMoreActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openDetailsReviewActivity(app: App) { + val intent = Intent( + this, + DetailsReviewActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openStreamBrowseActivity(browseUrl: String) { + val intent = Intent(this, StreamBrowseActivity::class.java) + intent.putExtra(Constants.BROWSE_EXTRA, browseUrl) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } + + fun openGoogleActivity() { + val intent = Intent(this, GoogleActivity::class.java) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } + + fun showNetworkConnectivitySheet() { + supportFragmentManager.beginTransaction() + .add(NetworkDialogSheet.newInstance(0), "NDS") + .commitAllowingStateLoss() + } + + fun hideNetworkConnectivitySheet() { + val fragment: Fragment? = supportFragmentManager.findFragmentByTag("NDS") + if (fragment != null) { + supportFragmentManager.beginTransaction() + .remove(fragment) + .commitAllowingStateLoss() + } + } + + override fun onStart() { + super.onStart() + NetworkProvider.addListener(this) + + } + + override fun onStop() { + NetworkProvider.removeListener(this) + super.onStop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt new file mode 100644 index 000000000..d8c414009 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt @@ -0,0 +1,74 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.util.ViewUtil +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +open class BaseFragment : Fragment() { + + protected lateinit var app: App + + var gson: Gson = GsonBuilder().excludeFieldsWithModifiers( + Modifier.TRANSIENT + ).create() + + fun openDetailsActivity(app: App) { + val intent = Intent(context, AppDetailsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, Gson().toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity()) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openCategoryBrowseActivity(category: Category) { + val intent = Intent(context, CategoryBrowseActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, category.title) + intent.putExtra(Constants.BROWSE_EXTRA, category.browseUrl) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity()) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openStreamBrowseActivity(browseUrl: String) { + val intent = Intent(requireContext(), StreamBrowseActivity::class.java) + intent.putExtra(Constants.BROWSE_EXTRA, browseUrl) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(requireContext()) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt new file mode 100644 index 000000000..4a894cfaa --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.store.R +import com.aurora.store.data.model.Black +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.BlackViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.viewmodel.all.BlacklistViewModel + + +class BlacklistActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var VM: BlacklistViewModel + private lateinit var blacklistProvider: BlacklistProvider + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(BlacklistViewModel::class.java) + blacklistProvider = BlacklistProvider.with(this) + + setContentView(B.root) + + VM.liveData.observe(this, { + updateController(it.sortedByDescending { app -> + blacklistProvider.isBlacklisted(app.packageName) + }) + }) + + attachToolbar() + + updateController(null) + } + + override fun onDestroy() { + blacklistProvider.save(VM.selected) + super.onDestroy() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_blacklist_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun updateController(blackList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (blackList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + blackList.forEach { + add( + BlackViewModel_() + .id(it.packageName.hashCode()) + .black(it) + .markChecked(VM.selected.contains(it.packageName)) + .checked { _, isChecked -> + if (isChecked) + VM.selected.add(it.packageName) + else + VM.selected.remove(it.packageName) + + requestModelBuild() + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt new file mode 100644 index 000000000..7ef64d098 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.CategoryCarouselController +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.ui.sheets.AppPeekDialogSheet +import com.aurora.store.viewmodel.subcategory.SubCategoryClusterViewModel + + +class CategoryBrowseActivity : BaseActivity(), GenericCarouselController.Callbacks { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var C: GenericCarouselController + lateinit var VM: SubCategoryClusterViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + lateinit var title: String + lateinit var homeUrl: String + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + C = CategoryCarouselController(this) + VM = ViewModelProvider(this).get(SubCategoryClusterViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + attachRecycler() + + intent.apply { + homeUrl = getStringExtra(Constants.BROWSE_EXTRA).toString() + title = getStringExtra(Constants.STRING_EXTRA).toString() + VM.observeCategory(homeUrl) + updateTitle(title) + } + + updateController(null) + } + + private fun updateTitle(title: String) { + B.layoutToolbarAction.txtTitle.text = title + } + + private fun attachToolbar() { + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + + B.recycler.setController(C) + + VM.liveData.observe(this, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + updateController(null) + } + is ViewState.Error -> { + + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + } + }) + + endlessRecyclerOnScrollListener = + object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + + endlessRecyclerOnScrollListener.disable() + + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamBundle: StreamBundle?) { + if (streamBundle != null) + endlessRecyclerOnScrollListener.enable() + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(this, "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + AppPeekDialogSheet.newInstance(app).show(supportFragmentManager, "APDS") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt new file mode 100644 index 000000000..cff479aa3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt @@ -0,0 +1,101 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.R +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.view.epoxy.views.CategoryViewModel_ +import com.aurora.store.viewmodel.category.AppCategoryViewModel +import com.aurora.store.viewmodel.category.BaseCategoryViewModel +import com.aurora.store.viewmodel.category.GameCategoryViewModel + + +class CategoryFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var VM: BaseCategoryViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): CategoryFragment { + return CategoryFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(AppCategoryViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(GameCategoryViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + } + + private fun updateController(categoryList: List) { + B.recycler.withModels { + setFilterDuplicates(true) + categoryList.forEach { + add( + CategoryViewModel_() + .id(it.title) + .category(it) + .click { _ -> openCategoryBrowseActivity(it) } + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt new file mode 100644 index 000000000..606cec963 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt @@ -0,0 +1,141 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.EarlyAccessCarouselController +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.viewmodel.homestream.BaseClusterViewModel +import com.aurora.store.viewmodel.homestream.EarlyAccessAppsViewModel +import com.aurora.store.viewmodel.homestream.EarlyAccessGamesViewModel + + +class EarlyAccessFragment : BaseFragment(), GenericCarouselController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: GenericCarouselController + private lateinit var VM: BaseClusterViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): EarlyAccessFragment { + return EarlyAccessFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = EarlyAccessCarouselController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(EarlyAccessAppsViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(EarlyAccessGamesViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + } + is ViewState.Error -> { + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + else -> { + + } + } + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + }) + } + + private fun updateController(streamBundle: StreamBundle) { + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(requireContext(), "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + Toast.makeText(requireContext(), app.displayName, Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt new file mode 100644 index 000000000..7452c9fde --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.epoxy.controller.EditorChoiceController +import com.aurora.store.viewmodel.editorschoice.AppEditorChoiceViewModel +import com.aurora.store.viewmodel.editorschoice.BaseEditorChoiceViewModel +import com.aurora.store.viewmodel.editorschoice.GameEditorChoiceViewModel + +class EditorChoiceFragment : Fragment(), EditorChoiceController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: EditorChoiceController + private lateinit var VM: BaseEditorChoiceViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): EditorChoiceFragment { + return EditorChoiceFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = EditorChoiceController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(AppEditorChoiceViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(GameEditorChoiceViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + } + + private fun updateController(editorChoiceBundles: List) { + C.setData(editorChoiceBundles) + } + + override fun onClick(editorChoiceCluster: EditorChoiceCluster) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt new file mode 100644 index 000000000..f27390f39 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.ui.sheets.AppPeekDialogSheet +import com.aurora.store.viewmodel.homestream.AppsForYouViewModel +import com.aurora.store.viewmodel.homestream.BaseClusterViewModel +import com.aurora.store.viewmodel.homestream.GamesForYouViewModel + + +class ForYouFragment : BaseFragment(), GenericCarouselController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: GenericCarouselController + private lateinit var VM: BaseClusterViewModel + + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): ForYouFragment { + return ForYouFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = GenericCarouselController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = + ViewModelProvider(requireActivity()).get(AppsForYouViewModel::class.java) + 1 -> VM = + ViewModelProvider(requireActivity()).get(GamesForYouViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + updateController(null) + } + is ViewState.Error -> { + + } + is ViewState.Status -> { + + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + } + }) + + endlessRecyclerOnScrollListener = + object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamBundle: StreamBundle?) { + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(requireContext(), "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + AppPeekDialogSheet.newInstance(app).show(parentFragmentManager, "APDS") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt new file mode 100644 index 000000000..1525cd0fc --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.viewmodel.browse.StreamBrowseViewModel + + +class StreamBrowseActivity : BaseActivity() { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var VM: StreamBrowseViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + lateinit var title: String + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(StreamBrowseViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + attachRecycler() + + VM.liveData.observe(this, { + updateController(it) + updateTitle(it) + }) + + intent.apply { + getStringExtra(Constants.BROWSE_EXTRA)?.let { + VM.getStreamBundle(it) + } + } + + updateController(null) + } + + private fun updateTitle(streamCluster: StreamCluster) { + B.layoutToolbarAction.txtTitle.text = streamCluster.clusterTitle + } + + private fun attachToolbar() { + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { + add( + AppListViewModel_() + .id(it.packageName.hashCode()) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt new file mode 100644 index 000000000..bc170151e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt @@ -0,0 +1,579 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.Manifest +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.LinearLayout +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.gplayapi.helpers.PurchaseHelper +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.downloader.RequestBuilder +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityDetailsBinding +import com.aurora.store.util.* +import com.aurora.store.util.extensions.* +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions +import com.tonyodev.fetch2.* +import com.tonyodev.fetch2core.DownloadBlock +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import java.util.* + +class AppDetailsActivity : BaseDetailsActivity() { + + private lateinit var B: ActivityDetailsBinding + private lateinit var bottomSheetBehavior: BottomSheetBehavior + + private lateinit var app: App + private lateinit var downloadManager: DownloadManager + private lateinit var fetch: Fetch + private lateinit var fetchGroupListener: FetchGroupListener + + private var isNone = false + private var status = Status.NONE + private var isInstalled: Boolean = false + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + EventBus.getDefault().unregister(this) + super.onStop() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + attachActions() + } + is BusEvent.UninstallEvent -> { + attachActions() + } + else -> { + + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsBinding.inflate(layoutInflater) + setContentView(B.root) + + onNewIntent(intent) + } + + override fun onResume() { + if (!isLAndAbove()) { + checkAndSetupInstall() + } + super.onResume() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + isInstalled = PackageUtil.isInstalled(this, app.packageName) + + inflatePartialApp() + + fetchCompleteApp() + } else { + close() + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_details, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_share -> { + share(app) + return true + } + R.id.action_uninstall -> { + uninstallApp() + return true + } + R.id.menu_download_manager -> { + open(DownloadActivity::class.java) + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutDetailsToolbar.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.title = "" + } + } + + private fun attachActions() { + flip(0) + checkAndSetupInstall() + } + + private fun openApp() { + val intent = PackageUtil.getLaunchIntent(this, app.packageName) + if (intent != null) { + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + toast("Unable to open app") + } + } + } + + @Synchronized + private fun install(files: List) { + task { + AppInstaller.with(this) + .getPreferredInstaller() + .install( + app.packageName, + files + .filter { it.file.endsWith(".apk") } + .map { + it.file + }.toList() + ) + } + + runOnUiThread { + B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing)) + } + } + + @Synchronized + private fun uninstallApp() { + AppInstaller.with(this) + .getPreferredInstaller() + .uninstall(app.packageName) + } + + private fun attachWhiteListStatus() { + + } + + private fun fetchCompleteApp() { + task { + val authData = AuthProvider.with(this).getAuthData() + return@task AppDetailsHelper.with(authData) + .using(HttpClient.getPreferredClient()) + .getAppByPackageName(app.packageName) + } successUi { + inflateExtraDetails(it) + } failUi { + toast("Failed to fetch app details") + } + } + + private fun inflatePartialApp() { + attachWhiteListStatus() + attachHeader() + attachToolbar() + attachBottomSheet() + attachFetch() + attachActions() + } + + private fun attachHeader() { + B.layoutDetailsApp.apply { + imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + txtLine1.text = app.displayName + txtLine2.text = app.developerName + txtLine2.setOnClickListener { + NavigationUtil.openDevAppsActivity( + this@AppDetailsActivity, + app + ) + } + txtLine3.text = ("v${app.versionName}.${app.versionCode}") + + val tags = mutableListOf() + if (app.isFree) + tags.add(getString(R.string.details_free)) + else + tags.add(getString(R.string.details_paid)) + + if (app.containsAds) + tags.add(getString(R.string.details_contains_ads)) + else + tags.add(getString(R.string.details_no_ads)) + + txtLine4.text = tags.joinToString(separator = " • ") + } + } + + private fun inflateExtraDetails(app: App?) { + app?.let { + B.viewFlipper.displayedChild = 1 + inflateAppDescription(B.layoutDetailDescription, app) + inflateAppRatingAndReviews(B.layoutDetailsReview, app) + inflateAppDevInfo(B.layoutDetailsDev, app) + inflateAppPrivacy(B.layoutDetailsPrivacy, app) + } + } + + @Synchronized + private fun startDownload() { + when (status) { + Status.PAUSED -> { + fetch.resumeGroup(app.id) + isNone = false + } + Status.NONE, Status.CANCELLED -> { + fetch.deleteGroup(app.id) + isNone = true + } + Status.ADDED -> isNone = false + Status.DOWNLOADING -> { + isNone = false + flip(1) + toast("Already downloading") + } + Status.COMPLETED -> { + fetch.getFetchGroup(app.id) { + install(it.downloads) + } + } + else -> { + } + } + if (isNone) { + purchase() + } + } + + private fun purchase() { + task { + val authData = AuthProvider + .with(this) + .getAuthData() + + PurchaseHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + .purchase(app.packageName, app.versionCode, app.offerType) + } successUi { + if (it.isNotEmpty()) { + enqueue(it) + } else { + Log.e("Failed to download : ${app.displayName}") + } + } failUi { + expandBottomSheet(it.message) + Log.e("Failed to purchase ${app.displayName} : ${it.message}") + } + } + + private fun enqueue(files: List) = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + val requestList = files + .filter { it.url.isNotEmpty() } + .map { + RequestBuilder.buildRequest(this, app, it) + } + .toList() + + if (requestList.isNotEmpty()) { + fetch.enqueue( + requestList + ) { + status = Status.ADDED + Log.i("Downloading Apks : %s", app.displayName) + } + } else { + expandBottomSheet(getString(R.string.purchase_no_file)) + } + } + + private fun updateProgress( + fetchGroup: FetchGroup, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long + ) { + runOnUiThread { + val progress = if (fetchGroup.groupDownloadProgress > 0) + fetchGroup.groupDownloadProgress + else + 0 + + B.layoutDetailsInstall.apply { + txtProgressPercent.text = ("${progress}%") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressDownload.setProgress(progress, true) + } else { + progressDownload.progress = progress + } + + txtEta.text = CommonUtil.getETAString( + this@AppDetailsActivity, + etaInMilliSeconds + ) + txtSpeed.text = + CommonUtil.getDownloadSpeedString( + this@AppDetailsActivity, + downloadedBytesPerSecond + ) + } + } + } + + private fun expandBottomSheet(message: String?) { + bottomSheetBehavior.isHideable = false + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + + B.layoutDetailsInstall.txtPurchaseError.text = message + B.layoutDetailsInstall.btnDownload.updateProgress(false) + } + + private fun checkAndSetupInstall() { + isInstalled = PackageUtil.isInstalled(this, app.packageName) + + B.layoutDetailsInstall.btnDownload.let { btn -> + if (isInstalled) { + val isUpdatable = PackageUtil.isUpdatable( + this, + app.packageName, + app.versionCode.toLong() + ) + + if (isUpdatable) { + btn.setText(getString(R.string.action_update)) + btn.addOnClickListener { + btn.updateProgress(true) + startDownload() + } + } else { + btn.setText(getString(R.string.action_open)) + btn.addOnClickListener { openApp() } + } + } else { + if (app.isFree) { + btn.setText(getString(R.string.action_install)) + } else { + btn.setText(app.price) + } + + btn.addOnClickListener { + btn.setText(getString(R.string.download_metadata)) + btn.updateProgress(true) + startDownload() + } + } + + btn.updateProgress(false) + } + } + + @Synchronized + private fun flip(nextView: Int) { + runOnUiThread { + B.layoutDetailsInstall.viewFlipper.displayedChild = nextView + if (nextView == 0) + checkAndSetupInstall() + } + } + + private fun attachFetch() { + downloadManager = DownloadManager.with(this) + fetch = downloadManager.fetch + + fetch.getFetchGroup(app.id) { fetchGroup: FetchGroup -> + if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) { + status = Status.COMPLETED + } else if (downloadManager.isDownloading(fetchGroup)) { + status = Status.DOWNLOADING + flip(1) + } else if (downloadManager.isCanceled(fetchGroup)) { + status = Status.CANCELLED + } else if (fetchGroup.pausedDownloads.isNotEmpty()) { + status = Status.PAUSED + } else { + status = Status.NONE + } + } + + fetchGroupListener = object : AbstractFetchGroupListener() { + + override fun onStarted( + groupId: Int, + download: Download, + downloadBlocks: List, + totalBlocks: Int, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + status = download.status + flip(1) + } + } + + override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(1) + } + } + + override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + + override fun onProgress( + groupId: Int, + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond) + Log.i( + "${app.displayName} : ${download.file} -> Progress : %d", + fetchGroup.groupDownloadProgress + ) + } + } + + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) { + status = download.status + flip(0) + install(fetchGroup.downloads) + updateProgress(fetchGroup, -1, -1) + } + } + + override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + + override fun onError( + groupId: Int, + download: Download, + error: Error, + throwable: Throwable?, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + } + + fetch.addListener(fetchGroupListener) + + B.layoutDetailsInstall.imgCancel.setOnClickListener { + fetch.cancelGroup( + app.id + ) + } + } + + private fun attachBottomSheet() { + B.layoutDetailsInstall.apply { + viewFlipper.setInAnimation(this@AppDetailsActivity, R.anim.fade_in) + viewFlipper.setOutAnimation(this@AppDetailsActivity, R.anim.fade_out) + } + + bottomSheetBehavior = BottomSheetBehavior.from(B.layoutDetailsInstall.bottomSheet) + bottomSheetBehavior.isDraggable = false + + bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetBehavior.setDraggable(true) + } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { + bottomSheetBehavior.isDraggable = false + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt new file mode 100644 index 000000000..fe651d72b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt @@ -0,0 +1,329 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import android.text.Html +import android.text.Layout +import android.view.View +import android.widget.RelativeLayout +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.text.HtmlCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Review +import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.store.R +import com.aurora.store.data.model.ExodusReport +import com.aurora.store.data.model.Report +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.LayoutDetailsDescriptionBinding +import com.aurora.store.databinding.LayoutDetailsDevBinding +import com.aurora.store.databinding.LayoutDetailsPrivacyBinding +import com.aurora.store.databinding.LayoutDetailsReviewBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.NavigationUtil +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.custom.RatingView +import com.aurora.store.view.epoxy.views.* +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.json.JSONObject +import java.util.* + +abstract class BaseDetailsActivity : BaseActivity() { + + private val exodusBaseUrl = "https://reports.exodus-privacy.eu.org/api/search/" + private val exodusApiKey = "Token bbe6ebae4ad45a9cbacb17d69739799b8df2c7ae" + + //Sub Section Inflation + fun inflateAppDescription(B: LayoutDetailsDescriptionBinding, app: App) { + B.txtInstalls.text = CommonUtil.addDiPrefix(app.installs) + B.txtSize.text = CommonUtil.addSiPrefix(app.size) + B.txtRating.text = app.labeledRating + B.txtSdk.text = String.format("Target SDK %s", "${app.targetSdk}") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.txtDescription.justificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD + } + + B.txtDescription.text = Html.fromHtml(app.shortDescription) + + app.changes.apply { + if (isEmpty()) { + B.txtChangelog.text = getString(R.string.details_changelog_unavailable) + } else { + B.txtChangelog.text = Html.fromHtml(this) + } + } + + B.headerDescription.addClickListener { + openDetailsMoreActivity(app) + } + + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + var position = 0 + app.screenshots + //.sortedWith { o1, o2 -> o2.height.compareTo(o1.height) } + .forEach { artwork -> + add( + ScreenshotViewModel_() + .id(artwork.url) + .artwork(artwork) + .position(position++) + .callback(object : ScreenshotView.ScreenshotCallback { + override fun onClick(position: Int) { + openScreenshotActivity(app, position) + } + }) + ) + } + } + } + + fun inflateAppRatingAndReviews(B: LayoutDetailsReviewBinding, app: App) { + B.averageRating.text = app.rating.average.toString() + B.txtReviewCount.text = app.rating.abbreviatedLabel + + var totalStars = 0L + totalStars += app.rating.oneStar + totalStars += app.rating.twoStar + totalStars += app.rating.threeStar + totalStars += app.rating.fourStar + totalStars += app.rating.fiveStar + + B.avgRatingLayout.apply { + removeAllViews() + addView(addAvgReviews(5, totalStars, app.rating.fiveStar)) + addView(addAvgReviews(4, totalStars, app.rating.fourStar)) + addView(addAvgReviews(3, totalStars, app.rating.threeStar)) + addView(addAvgReviews(2, totalStars, app.rating.twoStar)) + addView(addAvgReviews(1, totalStars, app.rating.oneStar)) + } + + B.averageRating.text = String.format(Locale.getDefault(), "%.1f", app.rating.average) + B.txtReviewCount.text = app.rating.abbreviatedLabel + + val authData = AuthProvider.with(this).getAuthData() + + B.btnPostReview.setOnClickListener { + if (authData.isAnonymous) { + toast(R.string.toast_anonymous_restriction) + } else { + addOrUpdateReview(B, app, Review().apply { + title = authData.userProfile!!.name + rating = B.userStars.rating.toInt() + comment = B.inputReview.text.toString() + }) + } + } + + B.headerRatingReviews.addClickListener { + openDetailsReviewActivity(app) + } + + task { + fetchFirstFewReviews(app) + } successUi { + B.epoxyRecycler.withModels { + it.take(4) + .forEach { + add( + ReviewViewModel_() + .id(it.timeStamp) + .review(it) + ) + } + } + } failUi { + + } + + } + + fun inflateAppPrivacy(B: LayoutDetailsPrivacyBinding, app: App) { + + task { + fetchReport(app.packageName) + } successUi { report -> + if (report.trackers.isNotEmpty()) { + B.txtStatus.apply { + setTextColor( + ContextCompat.getColor( + this@BaseDetailsActivity, + if (report.trackers.size > 4) + R.color.colorRed + else + R.color.colorOrange + ) + ) + text = + ("${report.trackers.size} ${getString(R.string.exodus_substring)} ${report.version}") + } + + B.headerPrivacy.addClickListener { + NavigationUtil.openExodusActivity(this, app, report) + } + } else { + B.txtStatus.apply { + setTextColor( + ContextCompat.getColor( + this@BaseDetailsActivity, + R.color.colorGreen + ) + ) + text = getString(R.string.exodus_no_tracker) + } + } + } failUi { + B.txtStatus.text = it.message + } + } + + fun inflateAppDevInfo(B: LayoutDetailsDevBinding, app: App) { + if (app.developerAddress.isNotEmpty()) { + B.devAddress.apply { + setTxtSubtitle( + HtmlCompat.fromHtml( + app.developerAddress, + HtmlCompat.FROM_HTML_MODE_LEGACY + ).toString() + ) + visibility = View.VISIBLE + } + } + + if (app.developerWebsite.isNotEmpty()) { + B.devWeb.apply { + setTxtSubtitle(app.developerWebsite) + visibility = View.VISIBLE + } + } + + if (app.developerEmail.isNotEmpty()) { + B.devWeb.apply { + setTxtSubtitle(app.developerEmail) + visibility = View.VISIBLE + } + } + } + + //Helpers + private fun openScreenshotActivity(app: App, position: Int) { + val intent = Intent( + this, + ScreenshotActivity::class.java + ).apply { + putExtra(Constants.STRING_EXTRA, gson.toJson(app.screenshots)) + putExtra(Constants.INT_EXTRA, position) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout { + return RatingView(this, number, max.toInt(), rating.toInt()) + } + + private fun addOrUpdateReview( + B: LayoutDetailsReviewBinding, + app: App, + review: Review, + isBeta: Boolean = false + ) { + task { + val authData = AuthProvider.with(this).getAuthData() + ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + .addOrEditReview( + app.packageName, + review.title, + review.comment, + review.rating, + isBeta + ) + }.successUi { + it?.let { + B.userStars.rating = it.rating.toFloat() + Toast.makeText(this, "Rated successfully", Toast.LENGTH_SHORT).show() + } + }.failUi { + Toast.makeText(this, "Failed to submit rating", Toast.LENGTH_SHORT).show() + } + } + + private fun fetchFirstFewReviews(app: App): List { + val authData = AuthProvider + .with(this) + .getAuthData() + val reviewsHelper = ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + return reviewsHelper.getReviews(app.packageName, Review.Filter.CRITICAL) + } + + /*---------------------------------- HELPERS FOR APP PRIVACY ---------------------------------*/ + + private fun parseResponse(response: String, packageName: String): List { + try { + val jsonObject = JSONObject(response) + val exodusObject = jsonObject.getJSONObject(packageName) + val exodusReport: ExodusReport = gson.fromJson( + exodusObject.toString(), + ExodusReport::class.java + ) + return exodusReport.reports + } catch (e: Exception) { + throw Exception("No reports found") + } + } + + private fun fetchReport(packageName: String): Report { + val headers: MutableMap = mutableMapOf() + headers["Content-Type"] = "application/json" + headers["Accept"] = "application/json" + headers["Authorization"] = exodusApiKey + + val url = exodusBaseUrl + packageName + + val playResponse = HttpClient + .getPreferredClient() + .get(url, headers) + + if (playResponse.isSuccessful) { + return parseResponse(String(playResponse.responseBytes), packageName)[0] + } else { + throw Exception("Failed to fetch report") + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt new file mode 100644 index 000000000..bc824178c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.model.ExodusTracker +import com.aurora.store.data.model.Report +import com.aurora.store.data.providers.ExodusDataProvider +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.ExodusViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import org.json.JSONObject + +class DetailsExodusActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var app: App + private lateinit var report: Report + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + + setContentView(B.root) + + val rawApp: String? = intent.getStringExtra(Constants.STRING_APP) + val rawExodusTrackers: String? = intent.getStringExtra(Constants.STRING_EXTRA) + + if (rawApp != null) { + app = gson.fromJson(rawApp, App::class.java) + report = gson.fromJson( + rawExodusTrackers, + Report::class.java + ) + app.let { + attachToolbar() + report.let { + updateController(getExodusTrackersFromReport(report)) + } + } + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.toolbar.setOnClickListener { + close() + } + B.layoutToolbarAction.txtTitle.text = app.displayName + } + + private fun updateController(reviews: List) { + B.recycler.withModels { + add( + HeaderViewModel_() + .id("header") + .title(getString(R.string.exodus_view_report)) + .browseUrl("browse") + .click { _ -> browse(Constants.EXODUS_REPORT_URL + report.id) } + ) + reviews.forEach { + add( + ExodusViewModel_() + .id(it.id) + .tracker(it) + .click { _ -> + browse(it.url) + } + ) + } + } + } + + private fun getExodusTrackersFromReport(report: Report): List { + val trackerObjects: List = fetchLocalTrackers(report.trackers) + return trackerObjects.map { + ExodusTracker().apply { + id = it.getInt("id") + name = it.getString("name") + url = it.getString("website") + signature = it.getString("code_signature") + date = it.getString("creation_date") + } + }.toList() + } + + private fun fetchLocalTrackers(trackerIds: List): List { + return try { + ExodusDataProvider + .with(this) + .getFilteredTrackers(trackerIds) + } catch (e: Exception) { + emptyList() + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt new file mode 100644 index 000000000..399ec68fc --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt @@ -0,0 +1,178 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.core.text.HtmlCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityDetailsMoreBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.FileViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppAltViewModel_ +import com.aurora.store.view.epoxy.views.details.AppDependentViewModel_ +import com.aurora.store.view.epoxy.views.details.MoreBadgeViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi + +class DetailsMoreActivity : BaseActivity() { + + private lateinit var B: ActivityDetailsMoreBinding + private lateinit var app: App + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsMoreBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + app.let { + inflateDescription(app) + inflateFiles(app) + fetchDependentApps(app) + } + } + } + + private fun attachToolbar() { + B.layoutToolbarActionMore.toolbar.setOnClickListener { + close() + } + } + + private fun inflateDescription(app: App) { + B.layoutToolbarActionMore.txtTitle.text = app.displayName + B.txtDescription.text = HtmlCompat.fromHtml( + app.description, + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + } + + private fun inflateFiles(app: App) { + B.recyclerMore.withModels { + //Add dependent files + if (app.fileList.isNotEmpty()) { + add( + HeaderViewModel_() + .id("badge_header") + .title("Files") + ) + + app.fileList.forEach { + add( + FileViewModel_() + .id(it.id) + .file(it) + ) + } + } + + //Add display & extra badges + if (app.infoBadges.isNotEmpty()) { + add( + HeaderViewModel_() + .id("badge_header") + .title("More") + ) + + app.infoBadges.forEach { + add( + MoreBadgeViewModel_() + .id(it.id) + .badge(it) + ) + } + + if (app.displayBadges.isNotEmpty()) { + app.displayBadges + .filter { it.textMajor.isNotEmpty() } + .forEach { + add( + MoreBadgeViewModel_() + .id(it.id) + .badge(it) + ) + } + } + } + } + } + + private fun fetchDependentApps(app: App) { + val authData: AuthData = AuthProvider + .with(this) + .getAuthData() + task { + AppDetailsHelper + .with(authData) + .getAppByPackageName(app.dependencies.dependentPackages) + } successUi { + B.recyclerDependency.withModels { + if (it.isNotEmpty()) { + it.filter { it.displayName.isNotEmpty() }.forEach { + add( + AppDependentViewModel_() + .id(it.id) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + } + } else { + add( + NoAppAltViewModel_() + .id("no_app") + .message(getString(R.string.details_no_dependencies)) + ) + } + } + } failUi { + B.recyclerDependency.withModels { + add( + NoAppAltViewModel_() + .id("no_app") + .message(getString(R.string.details_no_dependencies)) + ) + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt new file mode 100644 index 000000000..8fec8e22e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Review +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.ActivityDetailsReviewBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.ReviewViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.review.ReviewViewModel + +class DetailsReviewActivity : BaseActivity() { + + private lateinit var B: ActivityDetailsReviewBinding + private lateinit var VM: ReviewViewModel + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + private lateinit var app: App + private lateinit var filter: Review.Filter + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsReviewBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(ReviewViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + } + is ViewState.Error -> { + } + is ViewState.Success<*> -> { + updateController(it.data as List) + } + else -> { + + } + } + }) + + attachRecycler() + attachChips() + + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + filter = Review.Filter.ALL + + app.let { + attachToolbar() + fetchReviews() + } + } + } + + private fun attachToolbar() { + B.layoutToolbarActionReview.toolbar.setOnClickListener { + close() + } + B.layoutToolbarActionReview.txtTitle.text = app.displayName + } + + private fun attachChips() { + B.chipGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.filter_review_all -> filter = Review.Filter.ALL + 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 + R.id.filter_review_four -> filter = Review.Filter.FOUR + R.id.filter_review_three -> filter = Review.Filter.THREE + R.id.filter_review_two -> filter = Review.Filter.TWO + R.id.filter_review_one -> filter = Review.Filter.ONE + } + + resetPage() + fetchReviews() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.fetchReview(app.packageName, filter) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(reviews: List) { + B.recycler.withModels { + reviews.forEach { + add( + ReviewViewModel_() + .id(it.userName) + .review(it) + ) + } + } + } + + private fun fetchReviews() { + VM.fetchReview(app.packageName, filter) + } + + private fun resetPage() { + endlessRecyclerOnScrollListener.resetPageCount() + B.recycler.clear() + VM.reset() + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt new file mode 100644 index 000000000..23e1f5b1c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt @@ -0,0 +1,127 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.search.SearchResultViewModel + +class DevAppsActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var VM: SearchResultViewModel + + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + private lateinit var app: App + + var searchBundle: SearchBundle = SearchBundle() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchResultViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + searchBundle = it + updateController(searchBundle) + }) + + attachRecycler() + + onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + val rawApp: String? = intent.getStringExtra(Constants.STRING_APP) + app = gson.fromJson(rawApp, App::class.java) + app.let { + attachToolbar() + VM.observeSearchResults("pub:${app.developerName}") + } + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.toolbar.setOnClickListener { + close() + } + B.layoutToolbarAction.txtTitle.text = app.developerName + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next(searchBundle.subBundles) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(searchBundle: SearchBundle) { + B.recycler + .withModels { + setFilterDuplicates(true) + searchBundle.appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click(View.OnClickListener { + openDetailsActivity(app) + }) + ) + } + + if (searchBundle.subBundles.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt new file mode 100644 index 000000000..003c5a543 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt @@ -0,0 +1,92 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.PagerSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.aurora.Constants +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.databinding.ActivityScreenshotBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.LargeScreenshotViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.gson.reflect.TypeToken + +class ScreenshotActivity : BaseActivity() { + + private lateinit var B: ActivityScreenshotBinding + private lateinit var artworks: MutableList + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // ViewUtil.configureActivityLayout(this, false) + + B = ActivityScreenshotBinding.inflate(layoutInflater) + setContentView(B.root) + + attachRecycler() + + if (intent != null) { + val rawArtWorks = intent.getStringExtra(Constants.STRING_EXTRA).toString() + val position = intent.getIntExtra(Constants.INT_EXTRA, 0) + artworks = gson.fromJson(rawArtWorks, object : TypeToken?>() {}.type) + updateController(artworks, position) + } else { + close() + } + } + + private fun attachRecycler() { + B.recyclerView.apply { + layoutManager = LinearLayoutManager( + this@ScreenshotActivity, + RecyclerView.HORIZONTAL, + false + ) + PagerSnapHelper().attachToRecyclerView(this) + } + } + + private fun updateController(artworks: MutableList, position: Int) { + B.recyclerView.withModels { + artworks.forEach { + add( + LargeScreenshotViewModel_() + .id(it.url) + .artwork(it) + ) + } + B.recyclerView.scrollToPosition(position) + } + } + + override fun onConnected() { + + } + + override fun onDisconnected() { + } + + override fun onReconnected() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt new file mode 100644 index 000000000..04a9c8f52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt @@ -0,0 +1,243 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.downloads + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.model.DownloadFile +import com.aurora.store.databinding.ActivityDownloadBinding +import com.aurora.store.view.epoxy.views.DownloadViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.sheets.DownloadMenuSheet +import com.tonyodev.fetch2.* + +class DownloadActivity : BaseActivity() { + + private lateinit var B: ActivityDownloadBinding + private lateinit var fetch: Fetch + + private var fetchListener: FetchListener = object : AbstractFetchListener() { + override fun onAdded(download: Download) { + updateDownloadsList() + } + + override fun onQueued(download: Download, waitingOnNetwork: Boolean) { + updateDownloadsList() + } + + override fun onCompleted(download: Download) { + updateDownloadsList() + } + + override fun onError(download: Download, error: Error, throwable: Throwable?) { + updateDownloadsList() + } + + override fun onProgress( + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long + ) { + updateDownloadsList() + } + + override fun onPaused(download: Download) { + updateDownloadsList() + } + + override fun onResumed(download: Download) { + updateDownloadsList() + } + + override fun onCancelled(download: Download) { + updateDownloadsList() + } + + override fun onRemoved(download: Download) { + updateDownloadsList() + } + + override fun onDeleted(download: Download) { + updateDownloadsList() + } + } + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityDownloadBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + + fetch = DownloadManager.with(this).fetch + updateDownloadsList() + + B.swipeRefreshLayout.setOnRefreshListener { + updateDownloadsList() + } + } + + override fun onResume() { + super.onResume() + if (::fetch.isInitialized) + fetch.addListener(fetchListener) + } + + override fun onPause() { + if (::fetch.isInitialized) + fetch.removeListener(fetchListener) + super.onPause() + } + + override fun onDestroy() { + if (::fetch.isInitialized) + fetch.removeListener(fetchListener) + super.onDestroy() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_download_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_pause_all -> { + fetch.pauseAll() + return true + } + R.id.action_resume_all -> { + fetch.resumeAll() + return true + } + R.id.action_cancel_all -> { + fetch.cancelAll() + return true + } + R.id.action_clear_completed -> { + fetch.removeAllWithStatus(Status.COMPLETED) + return true + } + R.id.action_force_clear_all -> { + fetch.deleteAll() + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.setTitle(R.string.title_download_manager) + } + } + + private fun updateDownloadsList() { + if (::fetch.isInitialized) + fetch.getDownloads { downloads -> + updateController( + downloads + .sortedWith { o1, o2 -> o2.created.compareTo(o1.created) } + .map { DownloadFile(it) } + ) + } + } + + private fun updateController(downloads: List) { + B.recycler.withModels { + if (downloads.isEmpty()) { + add( + NoAppViewModel_() + .id("no_downloads") + .message(getString(R.string.download_none)) + ) + } else { + downloads.forEach { + add( + DownloadViewModel_() + .id(it.download.id, it.download.progress, it.download.status.value) + .download(it) + .click { _ -> openDetailsActivity(it) } + .longClick { _ -> + openDownloadMenuSheet(it) + false + } + ) + } + } + } + B.swipeRefreshLayout.isRefreshing = false + } + + private fun openDetailsActivity(downloadFile: DownloadFile) { + val app: App = gson.fromJson( + downloadFile.download.extras.getString(Constants.STRING_EXTRA, "{}"), + App::class.java + ) + openDetailsActivity(app) + } + + private fun openDownloadMenuSheet(downloadFile: DownloadFile) { + with(downloadFile) { + val fragment = supportFragmentManager.findFragmentByTag(DownloadMenuSheet.TAG) + if (fragment != null) + supportFragmentManager.beginTransaction().remove(fragment) + + DownloadMenuSheet().apply { + arguments = Bundle().apply { + putInt(DownloadMenuSheet.DOWNLOAD_ID, download.id) + putInt(DownloadMenuSheet.DOWNLOAD_STATUS, download.status.value) + putString(DownloadMenuSheet.DOWNLOAD_URL, download.url) + } + }.show( + supportFragmentManager, + DownloadMenuSheet.TAG + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt new file mode 100644 index 000000000..0850681bf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt @@ -0,0 +1,109 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentAppsGamesBinding +import com.aurora.store.view.ui.commons.CategoryFragment +import com.aurora.store.view.ui.commons.EarlyAccessFragment +import com.aurora.store.view.ui.commons.EditorChoiceFragment +import com.aurora.store.view.ui.commons.ForYouFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + + +class GamesContainerFragment : Fragment() { + + private lateinit var B: FragmentAppsGamesBinding + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentAppsGamesBinding.bind( + inflater.inflate( + R.layout.fragment_apps_games, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = "For you" + 1 -> tab.text = "Top charts" + 2 -> tab.text = "Categories" + 3 -> tab.text = "Early access" + 4 -> tab.text = "Editor choice" + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> ForYouFragment.newInstance(1) + 1 -> TopChartContainerFragment() + 2 -> CategoryFragment.newInstance(1) + 3 -> EarlyAccessFragment.newInstance(1) + 4 -> EditorChoiceFragment.newInstance(1) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 3 + else + 5 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt new file mode 100644 index 000000000..10c1b2e9e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentTopChartBinding + + +class TopChartContainerFragment : Fragment() { + + private lateinit var B: FragmentTopChartBinding + + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + B = FragmentTopChartBinding.bind( + inflater.inflate( + R.layout.fragment_top_chart, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle) + B.topTabGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.tab_top_free -> B.pager.setCurrentItem(0, true) + R.id.tab_top_grossing -> B.pager.setCurrentItem(1, true) + R.id.tab_trending -> B.pager.setCurrentItem(2, true) + R.id.tab_top_paid -> B.pager.setCurrentItem(3, true) + } + } + + B.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> B.topTabGroup.check(R.id.tab_top_free) + 1 -> B.topTabGroup.check(R.id.tab_top_grossing) + 2 -> B.topTabGroup.check(R.id.tab_trending) + 3 -> B.topTabGroup.check(R.id.tab_top_paid) + } + } + }) + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> TopChartFragment.newInstance(1, 0) + 1 -> TopChartFragment.newInstance(1, 1) + 2 -> TopChartFragment.newInstance(1, 2) + 3 -> TopChartFragment.newInstance(1, 3) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt new file mode 100644 index 000000000..c9c284562 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt @@ -0,0 +1,136 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentTopContainerBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.topchart.* + +class TopChartFragment : BaseFragment() { + + private lateinit var VM: BaseChartViewModel + private lateinit var B: FragmentTopContainerBinding + + private var chartType = 1 + private var chartCategory = 0 + + companion object { + @JvmStatic + fun newInstance(chartType: Int, chartCategory: Int): TopChartFragment { + return TopChartFragment().apply { + arguments = Bundle().apply { + putInt(Constants.TOP_CHART_TYPE, chartType) + putInt(Constants.TOP_CHART_CATEGORY, chartCategory) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + B = FragmentTopContainerBinding.bind( + inflater.inflate( + R.layout.fragment_top_container, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) + chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) + } + + when (chartCategory) { + 0 -> VM = + ViewModelProvider(requireActivity()).get(TopFreeGameChartViewModel::class.java) + 1 -> VM = + ViewModelProvider(requireActivity()).get(TopGrossingGameChartViewModel::class.java) + 2 -> VM = + ViewModelProvider(requireActivity()).get(TrendingGameChartViewModel::class.java) + 3 -> VM = + ViewModelProvider(requireActivity()).get(TopPaidGameChartViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + }) + + updateController(null) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt new file mode 100644 index 000000000..e7f71001c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt @@ -0,0 +1,195 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewAnimationUtils +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Accent +import com.aurora.store.databinding.FragmentOnboardingAccentBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.extensions.hide +import com.aurora.store.util.extensions.isVisible +import com.aurora.store.util.extensions.show +import com.aurora.store.util.save +import com.aurora.store.view.custom.CubicBezierInterpolator +import com.aurora.store.view.epoxy.views.AccentViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.gara.store.view.epoxy.controller.FlexLayoutManager +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.JustifyContent +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets +import kotlin.math.sqrt + + +class AccentFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingAccentBinding + + private var themeId: Int = 0 + private var accentId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingAccentBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_accent, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + themeId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_TYPE + ) + + accentId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_ACCENT + ) + + attachRecycler() + + val accentList = loadAccentsFromAssets() + updateController(accentList) + } + + private fun attachRecycler() { + with(B.epoxyRecycler) { + layoutManager = FlexLayoutManager(requireContext()).apply { + justifyContent = JustifyContent.SPACE_BETWEEN + flexWrap = FlexWrap.WRAP + flexDirection = FlexDirection.ROW + } + } + } + + private fun updateController(accentList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + + accentList.forEach { + add( + AccentViewModel_() + .id(it.id) + .accent(it) + .markChecked(accentId == it.id) + .click { v -> + accentId = it.id + updateAccent(accentId) + requestModelBuild() + animate(v) + } + ) + } + } + } + + private fun updateAccent(accentId: Int) { + applyTheme(themeId, accentId, position = 3) + save(PREFERENCE_THEME_ACCENT, accentId) + } + + private fun animate(view: View) { + if (Build.VERSION.SDK_INT >= 21) { + if (B.themeSwitchImage.isVisible()) { + return; + } + try { + val pos = IntArray(2) + view.getLocationInWindow(pos) + val w: Int = B.root.measuredWidth + val h: Int = B.root.measuredHeight + + val bitmap = Bitmap.createBitmap( + B.root.measuredWidth, + B.root.measuredHeight, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + B.root.draw(canvas) + B.themeSwitchImage.setImageBitmap(bitmap) + B.themeSwitchImage.show() + + val finalRadius = sqrt( + ((w - pos[0]) * (w - pos[0]) + (h - pos[1]) * (h - pos[1])).toDouble() + ).coerceAtLeast( + sqrt((pos[0] * pos[0] + (h - pos[1]) * (h - pos[1])).toDouble()) + ).toFloat() + + val anim: Animator = ViewAnimationUtils.createCircularReveal( + B.root, + pos[0], + pos[1], + 0f, + finalRadius + ) + + anim.duration = 450 + anim.interpolator = CubicBezierInterpolator.EASE_IN_OUT_QUAD + anim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + B.themeSwitchImage.setImageDrawable(null) + B.themeSwitchImage.hide() + } + }) + anim.start() + } catch (ignore: Throwable) { + } + } + } + + private fun loadAccentsFromAssets(): List { + val inputStream = requireContext().assets.open("accent.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt new file mode 100644 index 000000000..666ef5a97 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt @@ -0,0 +1,128 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Installer +import com.aurora.store.databinding.FragmentOnboardingInstallerBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID +import com.aurora.store.util.extensions.runOnUiThread +import com.aurora.store.util.extensions.toast +import com.aurora.store.util.save +import com.aurora.store.view.epoxy.views.preference.InstallerViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import com.topjohnwu.superuser.Shell +import java.nio.charset.StandardCharsets + + +class InstallerFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingInstallerBinding + + var installerId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingInstallerBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_installer, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + installerId = Preferences.getInteger( + requireContext(), + PREFERENCE_INSTALLER_ID + ) + + val installerList = loadInstallersFromAssets() + updateController(installerList) + } + + private fun updateController(installerList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + installerList.forEach { + add( + InstallerViewModel_() + .id(it.id) + .installer(it) + .markChecked(installerId == it.id) + .checked { _, checked -> + if (checked) { + installerId = it.id + save(installerId) + requestModelBuild() + } + } + ) + } + } + } + + private fun save(installerId: Int) { + if (installerId == 2) { + checkRoot() + } + save(PREFERENCE_INSTALLER_ID, installerId) + } + + private fun checkRoot() { + Shell.getShell { + runOnUiThread { + requireContext().toast( + if (it.isRoot) + getString(R.string.installer_root_available) + else + getString(R.string.installer_root_unavailable) + ) + } + } + } + + private fun loadInstallersFromAssets(): List { + val inputStream = requireContext().assets.open("installers.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt new file mode 100644 index 000000000..b49754f5c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt @@ -0,0 +1,145 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback +import com.aurora.Constants +import com.aurora.store.R +import com.aurora.store.databinding.ActivityOnboardingBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INTRO +import com.aurora.store.util.extensions.open +import com.aurora.store.util.save +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.splash.SplashActivity +import com.google.android.material.tabs.TabLayoutMediator + +class OnboardingActivity : BaseActivity() { + + lateinit var B: ActivityOnboardingBinding + + override fun onConnected() {} + + override fun onDisconnected() {} + + override fun onReconnected() {} + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val isIntroDone = Preferences.getBoolean(this, PREFERENCE_INTRO) + if (isIntroDone) + runOnUiThread { open(SplashActivity::class.java, true) } + + B = ActivityOnboardingBinding.inflate(layoutInflater) + + setContentView(B.root) + + attachViewPager() + + B.btnForward.setOnClickListener { + moveForward() + } + + B.btnBackward.setOnClickListener { + moveBackward() + } + + onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + val pos = intent.getIntExtra(Constants.INT_EXTRA, 0) + B.viewpager2.setCurrentItem(pos, false) + + } + } + + private fun attachViewPager() { + B.viewpager2.adapter = PagerAdapter(this) + B.viewpager2.isUserInputEnabled = false + B.viewpager2.setCurrentItem(0, true) + B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + runOnUiThread { + B.btnBackward.isEnabled = position != 0 + if (position == 3) { + B.btnForward.text = getString(R.string.action_finish) + B.btnForward.setOnClickListener { + save(PREFERENCE_INTRO, true) + open(SplashActivity::class.java, true) + } + } else { + B.btnForward.text = getString(R.string.action_next) + B.btnForward.setOnClickListener { + B.viewpager2.setCurrentItem( + B.viewpager2.currentItem + 1, true + ) + } + } + } + } + }) + + TabLayoutMediator(B.tabLayout, B.viewpager2, true) { tab, position -> + tab.text = (position + 1).toString() + }.attach() + } + + private fun moveForward() { + B.viewpager2.setCurrentItem(B.viewpager2.currentItem + 1, true) + } + + private fun moveBackward() { + B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true) + } + + override fun onBackPressed() { + if (B.viewpager2.currentItem == 0) { + super.onBackPressed() + } else { + B.viewpager2.currentItem = B.viewpager2.currentItem - 1 + } + } + + internal class PagerAdapter(fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + override fun createFragment(position: Int): Fragment { + when (position) { + 0 -> return WelcomeFragment() + 1 -> return InstallerFragment() + 2 -> return ThemeFragment() + 3 -> return AccentFragment() + } + return Fragment() + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt new file mode 100644 index 000000000..63783e452 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt @@ -0,0 +1,180 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewAnimationUtils +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Theme +import com.aurora.store.databinding.FragmentOnboardingThemeBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.extensions.hide +import com.aurora.store.util.extensions.isVisible +import com.aurora.store.util.extensions.show +import com.aurora.store.util.save +import com.aurora.store.view.custom.CubicBezierInterpolator +import com.aurora.store.view.epoxy.views.ThemeViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets +import kotlin.math.sqrt + + +class ThemeFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingThemeBinding + + private var themeId: Int = 0 + private var accentId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingThemeBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_theme, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + themeId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_TYPE + ) + + accentId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_ACCENT + ) + + val themeList = loadThemesFromAssets() + updateController(themeList) + } + + private fun updateController(themeList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + themeList.forEach { + add( + ThemeViewModel_() + .id(it.id) + .theme(it) + .markChecked(themeId == it.id) + .checked { v, checked -> + if (checked) { + themeId = it.id + update(themeId) + requestModelBuild() + animate(v) + } + } + ) + } + } + } + + private fun update(themeId: Int) { + applyTheme(themeId) + save(PREFERENCE_THEME_TYPE, themeId) + } + + private fun animate(view: View) { + if (Build.VERSION.SDK_INT >= 21) { + if (B.themeSwitchImage.isVisible()) { + return; + } + try { + val pos = IntArray(2) + view.getLocationInWindow(pos) + val w: Int = B.root.measuredWidth + val h: Int = B.root.measuredHeight + + val bitmap = Bitmap.createBitmap( + B.root.measuredWidth, + B.root.measuredHeight, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + B.root.draw(canvas) + B.themeSwitchImage.setImageBitmap(bitmap) + B.themeSwitchImage.show() + + val finalRadius = sqrt( + ((w - pos[0]) * (w - pos[0]) + (h - pos[1]) * (h - pos[1])).toDouble() + ).coerceAtLeast( + sqrt((pos[0] * pos[0] + (h - pos[1]) * (h - pos[1])).toDouble()) + ).toFloat() + + val anim: Animator = ViewAnimationUtils.createCircularReveal( + B.root, + pos[0], + pos[1], + 0f, + finalRadius + ) + + anim.duration = 450 + anim.interpolator = CubicBezierInterpolator.EASE_IN_OUT_QUAD + anim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + B.themeSwitchImage.setImageDrawable(null) + B.themeSwitchImage.hide() + } + }) + anim.start() + } catch (ignore: Throwable) { + } + } + } + + private fun loadThemesFromAssets(): List { + val inputStream = requireContext().assets.open("themes.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt new file mode 100644 index 000000000..fdbc4e99b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Dash +import com.aurora.store.databinding.FragmentOnboardingWelcomeBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.view.epoxy.views.preference.DashViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets + +class WelcomeFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingWelcomeBinding + + companion object { + @JvmStatic + fun newInstance(): WelcomeFragment { + return WelcomeFragment().apply { + + } + } + + val icMap: MutableMap = mutableMapOf( + "ic_faq" to R.drawable.ic_faq, + "ic_code" to R.drawable.ic_code, + "ic_license" to R.drawable.ic_license, + "ic_privacy" to R.drawable.ic_privacy, + "ic_disclaimer" to R.drawable.ic_disclaimer, + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingWelcomeBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_welcome, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val dashList = loadDashFromAssets() + updateController(dashList) + } + + private fun updateController(dashList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + dashList.forEach { + add( + DashViewModel_() + .id(it.id) + .dash(it) + .click { _ -> requireContext().browse(it.url) } + ) + } + } + } + + private fun loadDashFromAssets(): List { + val inputStream = requireContext().assets.open("dash.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt new file mode 100644 index 000000000..f1cb2a0c5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + + +class DownloadPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_download, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt new file mode 100644 index 000000000..827234e2f --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + +class FilterPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_filter, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt new file mode 100644 index 000000000..5244ebdc7 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt @@ -0,0 +1,54 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import android.view.View +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.Preferences +import com.aurora.store.util.extensions.runOnUiThread +import com.aurora.store.util.extensions.toast + + +class InstallationPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_installation, rootKey) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val abandonPreference: Preference? = + findPreference(Preferences.INSTALLATION_ABANDON_SESSION) + abandonPreference?.let { + it.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + CommonUtil.cleanupInstallationSessions(requireContext()) + runOnUiThread { + requireContext().toast(R.string.toast_abandon_sessions) + } + false + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt new file mode 100644 index 000000000..640584b4b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + + +class MainPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_main, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt new file mode 100644 index 000000000..94a5d39ea --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt @@ -0,0 +1,152 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.MenuItem +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.databinding.ActivitySettingBinding +import com.aurora.store.util.ViewUtil.getStyledAttribute +import com.aurora.store.util.extensions.restartApp +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class SettingsActivity : BaseActivity(), + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { + + private lateinit var B: ActivitySettingBinding + + companion object { + var shouldRestart = false + const val titleTag = "titleTag" + } + + override fun onConnected() { + } + + override fun onDisconnected() { + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySettingBinding.inflate(layoutInflater) + setContentView(B.root) + + if (savedInstanceState == null) { + supportFragmentManager + .beginTransaction() + .replace(R.id.settings, MainPreference()) + .commit() + } else { + title = savedInstanceState.getCharSequence(titleTag) + } + + supportFragmentManager.addOnBackStackChangedListener { + if (supportFragmentManager.backStackEntryCount == 0) { + B.layoutToolbarAction.toolbar.setTitle(R.string.title_settings) + if (shouldRestart) + askRestart() + } + } + + attachToolbar() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.setTitle(R.string.title_settings) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putCharSequence(titleTag, title) + } + + override fun onSupportNavigateUp(): Boolean { + if (supportFragmentManager.popBackStackImmediate()) { + return true + } + return super.onSupportNavigateUp() + } + + override fun onPreferenceStartFragment( + caller: PreferenceFragmentCompat, + preference: Preference + ): Boolean { + with(supportFragmentManager) { + val args = preference.extras + + val fragment = fragmentFactory.instantiate( + classLoader, + preference.fragment + ).apply { + arguments = args + setTargetFragment(caller, 0) + } + + beginTransaction() + .replace(R.id.settings, fragment) + .addToBackStack(null) + .commit() + + B.layoutToolbarAction.toolbar.title = preference.title + } + + return true + } + + private fun askRestart() { + val builder = MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.action_restart)) + .setMessage(getString(R.string.pref_dialog_to_apply_restart)) + .setPositiveButton(getString(R.string.action_restart)) { _, _ -> + shouldRestart = false + restartApp() + } + .setNegativeButton(getString(R.string.action_later)) { dialog, _ -> dialog.dismiss() } + val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground) + builder.background = ColorDrawable(backGroundColor) + builder.create() + builder.show() + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt new file mode 100644 index 000000000..cd353a2a1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import android.view.View +import androidx.preference.ListPreference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.util.Preferences +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.save + + +class UIPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_ui, rootKey) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val themePreference: ListPreference? = findPreference(Preferences.PREFERENCE_THEME_TYPE) + themePreference?.let { + it.setOnPreferenceChangeListener { _, newValue -> + val themeId = Integer.parseInt(newValue.toString()) + val accentId = Preferences.getInteger( + requireContext(), + Preferences.PREFERENCE_THEME_ACCENT + ) + + save(Preferences.PREFERENCE_THEME_TYPE, themeId) + + applyTheme(themeId, accentId, shouldApplyTransition = false) + + SettingsActivity.shouldRestart = true + true + } + } + + val accentPreference: ListPreference? = findPreference(Preferences.PREFERENCE_THEME_ACCENT) + accentPreference?.let { + it.setOnPreferenceChangeListener { _, newValue -> + val themeId = Preferences.getInteger( + requireContext(), + Preferences.PREFERENCE_THEME_TYPE + ) + val accentId = Integer.parseInt(newValue.toString()) + + save(Preferences.PREFERENCE_THEME_ACCENT, accentId) + + applyTheme(themeId, accentId, shouldApplyTransition = false) + + SettingsActivity.shouldRestart = true + true + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt b/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt new file mode 100644 index 000000000..521431f96 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt @@ -0,0 +1,121 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sale + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.sale.AppSalesViewModel + + +class AppSalesActivity : BaseActivity() { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var VM: AppSalesViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AppSalesViewModel::class.java) + + setContentView(B.root) + + attachRecycler() + attachToolbar() + + VM.liveAppList.observe(this, { + updateController(it) + }) + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_apps_sale) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler + .withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + appList + .filter { it.packageName.isNotEmpty() } + .forEach { + add( + AppListViewModel_() + .id(it.packageName.hashCode()) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + setFilterDuplicates(true) + } + + if (appList.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt new file mode 100644 index 000000000..3b8f89d52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.search + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.store.databinding.ActivitySearchResultBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.open +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.viewmodel.search.SearchResultViewModel +import com.google.android.material.textfield.TextInputEditText + + +class SearchResultsActivity : BaseActivity() { + + lateinit var B: ActivitySearchResultBinding + lateinit var VM: SearchResultViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + lateinit var searchView: TextInputEditText + + var query: String? = null + var searchBundle: SearchBundle = SearchBundle() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySearchResultBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchResultViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + searchBundle = it + updateController(searchBundle) + }) + + attachToolbar() + attachSearch() + attachRecycler() + + query = intent.getStringExtra(Constants.STRING_EXTRA) + query?.let { + updateQuery(it) + } + } + + private fun attachToolbar() { + searchView = B.layoutViewToolbar.inputSearch + + B.layoutViewToolbar.imgActionPrimary.setOnClickListener { + close() + } + B.layoutViewToolbar.imgActionSecondary.setOnClickListener { + open(DownloadActivity::class.java) + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next(searchBundle.subBundles) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(searchBundle: SearchBundle) { + B.recycler + .withModels { + setFilterDuplicates(true) + searchBundle.appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click(View.OnClickListener { + openDetailsActivity(app) + }) + ) + } + + if (searchBundle.subBundles.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + + private fun attachSearch() { + searchView.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()) { + /*val query = s.toString() + if (query.isNotEmpty()) { + VM.observeSearchResults(query) + }*/ + } + } + + override fun afterTextChanged(s: Editable) {} + }) + + searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + val query = searchView.text.toString() + if (query.isNotEmpty()) { + queryViewModel(query) + return@setOnEditorActionListener true + } + } + false + } + } + + private fun updateQuery(query: String) { + searchView.text = Editable.Factory.getInstance().newEditable(query) + searchView.setSelection(query.length) + queryViewModel(query) + } + + private fun queryViewModel(query: String) { + endlessRecyclerOnScrollListener.resetPageCount() + B.recycler.clear() + searchBundle.subBundles.clear() + searchBundle.appList.clear() + VM.observeSearchResults(query) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt b/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt new file mode 100644 index 000000000..b91d96b0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt @@ -0,0 +1,159 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.search + +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.store.databinding.ActivitySearchSuggestionBinding +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.open +import com.aurora.store.util.extensions.showKeyboard +import com.aurora.store.view.epoxy.views.SearchSuggestionViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.viewmodel.search.SearchSuggestionViewModel +import com.google.android.material.textfield.TextInputEditText + + +class SearchSuggestionActivity : BaseActivity() { + + lateinit var B: ActivitySearchSuggestionBinding + lateinit var VM: SearchSuggestionViewModel + + lateinit var searchView: TextInputEditText + + var query: String = String() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySearchSuggestionBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchSuggestionViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + + VM.liveSearchSuggestions.observe(this, { + updateController(it) + }) + + setupSearch() + } + + override fun onResume() { + super.onResume() + if (::searchView.isInitialized) { + searchView.showKeyboard() + } + } + + private fun attachToolbar() { + searchView = B.layoutToolbarSearch.inputSearch + + B.layoutToolbarSearch.imgActionPrimary.setOnClickListener { + close() + } + B.layoutToolbarSearch.imgActionSecondary.setOnClickListener { + open(DownloadActivity::class.java) + } + } + + private fun updateController(searchSuggestions: List) { + B.recycler.withModels { + setFilterDuplicates(true) + searchSuggestions.forEach { + add( + SearchSuggestionViewModel_() + .id(it.title) + .entry(it) + .action { _ -> + updateQuery(it.title) + } + .click { _ -> + search(it.title) + } + ) + } + } + } + + private fun setupSearch() { + searchView.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() + if (query.isNotEmpty()) { + VM.observeStreamBundles(query) + } + } + } + + override fun afterTextChanged(s: Editable) {} + }) + + searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + query = searchView.text.toString() + if (query.isNotEmpty()) { + search(query) + return@setOnEditorActionListener true + } + } + false + } + } + + private fun updateQuery(query: String) { + searchView.text = Editable.Factory.getInstance().newEditable(query) + searchView.setSelection(query.length) + } + + private fun search(query: String) { + val intent = Intent(this, SearchResultsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, query) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt new file mode 100644 index 000000000..f3d93f60d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt @@ -0,0 +1,143 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.Manifest +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.databinding.SheetAppMenuBinding +import com.aurora.store.util.ApkCopier +import com.aurora.store.util.PackageUtil +import com.aurora.store.util.extensions.isQAndAbove +import com.aurora.store.util.extensions.openInfo +import com.aurora.store.util.extensions.toast +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions +import nl.komponents.kovenant.task +import org.greenrobot.eventbus.EventBus + +class AppMenuSheet : BaseBottomSheet() { + + private lateinit var B: SheetAppMenuBinding + private lateinit var app: App + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { + B = SheetAppMenuBinding.inflate(layoutInflater) + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (arguments != null) { + val bundle = arguments + val stringExtra = bundle!!.getString(Constants.STRING_EXTRA) + app = gson.fromJson(stringExtra, App::class.java) + setupNavigationView() + } else { + dismissAllowingStateLoss() + } + } + + private fun setupNavigationView() { + + val blacklistProvider = BlacklistProvider.with(requireContext()) + val isBlacklisted: Boolean = blacklistProvider.isBlacklisted(app.packageName) + + with(B.navigationView) { + //Switch strings for Add/Remove Blacklist + val blackListMenu: MenuItem = menu.findItem(R.id.action_blacklist) + blackListMenu.setTitle( + if (isBlacklisted) + R.string.action_whitelist + else + R.string.action_blacklist_add + ) + + //Show/Hide actions based on installed status + val installed = PackageUtil.isInstalled(requireContext(), app.packageName) + menu.findItem(R.id.action_uninstall).isVisible = installed + menu.findItem(R.id.action_local).isVisible = installed + + if (isQAndAbove()) { + //TODO: Add Scoped Storage Access + menu.findItem(R.id.action_local).isVisible = false + } + + setNavigationItemSelectedListener { item -> + when (item.itemId) { + R.id.action_blacklist -> { + + if (isBlacklisted) { + blacklistProvider.whitelist(app.packageName) + requireContext().toast(R.string.toast_apk_whitelisted) + } else { + blacklistProvider.blacklist(app.packageName) + requireContext().toast(R.string.toast_apk_blacklisted) + } + + EventBus.getDefault() + .post(BusEvent.Blacklisted(app.packageName, "")) + } + + R.id.action_local -> { + export() + } + + R.id.action_uninstall -> { + AppInstaller + .with(requireContext()) + .getPreferredInstaller().uninstall(app.packageName) + } + + R.id.action_info -> { + requireContext().openInfo(app.packageName) + } + } + dismissAllowingStateLoss() + false + } + } + } + + private fun export() = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + task { + ApkCopier(requireContext(), app.packageName).copy() + } + } + + companion object { + const val TAG = "APP_MENU_SHEET" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt new file mode 100644 index 000000000..6c469e1cd --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt @@ -0,0 +1,96 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.SheetAppPeekBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +class AppPeekDialogSheet : BaseBottomSheet() { + + lateinit var B: SheetAppPeekBinding + lateinit var app: App + + private var rawApp = String() + + companion object { + @JvmStatic + fun newInstance(app: App): AppPeekDialogSheet { + return AppPeekDialogSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + } + } + } + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? { + B = SheetAppPeekBinding.inflate(inflater, container, false) + val bundle = arguments + if (bundle != null) { + rawApp = bundle.getString(Constants.STRING_EXTRA, "") + if (rawApp.isNotEmpty()) { + app = gson.fromJson(rawApp, App::class.java) + if (app.packageName.isNotEmpty()) { + draw() + } else { + dismissAllowingStateLoss() + } + } else { + dismissAllowingStateLoss() + } + } + + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + + } + + private fun draw() { + B.txtLine1.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + transform(RoundedCorners(25)) + } + B.txtLine2.text = app.developerName + B.txtLine3.text = String.format( + requireContext().getString(R.string.app_list_rating), + CommonUtil.addSiPrefix(app.size), + app.labeledRating, + if (app.isFree) + "Free" + else + "Paid" + ) + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt new file mode 100644 index 000000000..a65fbb73c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.aurora.store.R +import com.aurora.store.databinding.SheetBaseBinding +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +abstract class BaseBottomSheet : BottomSheetDialogFragment() { + + lateinit var VM: SheetBaseBinding + + var gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val bottomSheetDialog = BottomSheetDialog( + requireContext(), + R.style.Aurora_BottomSheetDialog + ) + + VM = SheetBaseBinding.inflate(layoutInflater) + + bottomSheetDialog.setContentView(VM.root) + + val container = VM.container + val contentView = onCreateContentView( + LayoutInflater.from(requireContext()), + container, + savedInstanceState + ) + + if (contentView != null) { + onContentViewCreated(contentView, savedInstanceState) + container.addView(contentView) + } + + bottomSheetDialog.setOnShowListener { + val bottomSheet = + bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) + if (bottomSheet != null) + BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED + } + return bottomSheetDialog + } + + abstract fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? + + abstract fun onContentViewCreated(view: View, savedInstanceState: Bundle?) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt new file mode 100644 index 000000000..25dee9d3c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.databinding.SheetDownloadMenuBinding +import com.aurora.store.util.extensions.copyToClipBoard +import com.aurora.store.util.extensions.toast +import com.tonyodev.fetch2.Fetch +import com.tonyodev.fetch2.Status + +class DownloadMenuSheet : BaseBottomSheet() { + + private lateinit var B: SheetDownloadMenuBinding + private lateinit var fetch: Fetch + + private var downloadId = 0 + private var status = 0 + private var url: String? = null + + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { + B = SheetDownloadMenuBinding.inflate(layoutInflater) + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fetch = DownloadManager + .with(requireContext()) + .getFetchInstance() + + if (arguments != null) { + val bundle = arguments + if (bundle != null) { + downloadId = bundle.getInt(DOWNLOAD_ID) + status = bundle.getInt(DOWNLOAD_STATUS) + url = bundle.getString(DOWNLOAD_URL) + attachNavigation() + } else { + dismissAllowingStateLoss() + } + } else { + dismissAllowingStateLoss() + } + } + + private fun attachNavigation() { + with(B.navigationView) { + if (status == Status.PAUSED.value || status == Status.COMPLETED.value || status == Status.CANCELLED.value) { + menu.findItem(R.id.action_pause).isVisible = false + } + + if (status == Status.DOWNLOADING.value || status == Status.COMPLETED.value || status == Status.QUEUED.value) { + menu.findItem(R.id.action_resume).isVisible = false + } + + if (status == Status.COMPLETED.value || status == Status.CANCELLED.value) { + menu.findItem(R.id.action_cancel).isVisible = false + } + + setNavigationItemSelectedListener { item -> + when (item.itemId) { + R.id.action_copy -> { + requireContext().copyToClipBoard(url) + requireContext().toast(requireContext().getString(R.string.toast_clipboard_copied)) + } + R.id.action_pause -> { + fetch.pause(downloadId) + } + R.id.action_resume -> if (status == Status.FAILED.value || status == Status.CANCELLED.value) { + fetch.retry(downloadId) + } else { + fetch.resume(downloadId) + } + R.id.action_cancel -> { + fetch.cancel(downloadId) + } + R.id.action_clear -> { + fetch.delete(downloadId) + } + } + dismissAllowingStateLoss() + false + } + } + } + + companion object { + const val TAG = "DOWNLOAD_MENU_SHEET" + const val DOWNLOAD_ID = "DOWNLOAD_ID" + const val DOWNLOAD_STATUS = "DOWNLOAD_STATUS" + const val DOWNLOAD_URL = "DOWNLOAD_URL" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt new file mode 100644 index 000000000..ecee09a52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt @@ -0,0 +1,64 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.store.databinding.SheetNetworkBinding + +class NetworkDialogSheet : BaseBottomSheet() { + + lateinit var B: SheetNetworkBinding + + private var type = 0 + + companion object { + @JvmStatic + fun newInstance(type: Int): NetworkDialogSheet { + return NetworkDialogSheet().apply { + arguments = Bundle().apply { + putInt(Constants.INT_EXTRA, type) + } + } + } + } + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? { + B = SheetNetworkBinding.inflate(inflater, container, false) + + val bundle = arguments + if (bundle != null) { + type = bundle.getInt(Constants.INT_EXTRA, 0) + } + + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt b/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt new file mode 100644 index 000000000..41872eba9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt @@ -0,0 +1,199 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.splash + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.data.AuthState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.databinding.ActivitySplashBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.open +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.commons.BlacklistActivity +import com.aurora.store.view.ui.spoof.SpoofActivity +import com.aurora.store.viewmodel.auth.AuthViewModel +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe + +class SplashActivity : BaseActivity() { + + private lateinit var VM: AuthViewModel + private lateinit var B: ActivitySplashBinding + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + EventBus.getDefault().register(this); + + B = ActivitySplashBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AuthViewModel::class.java) + + setContentView(B.root) + + B.imgIcon.load(R.drawable.ic_logo) { + transform(RoundedCorners(32)) + } + + attachToolbar() + attachActions() + + //Initial status + updateStatus("Getting things ready..") + + VM.liveData.observe(this, { + when (it) { + AuthState.Valid -> { + moveToContent() + } + + AuthState.Available -> { + updateStatus("Verifying session") + updateActionLayout(false) + } + + AuthState.Unavailable -> { + updateStatus("You need to login first") + updateActionLayout(true) + } + + AuthState.SignedIn -> { + moveToContent() + } + + AuthState.SignedOut -> { + updateStatus("Last session scrapped") + updateActionLayout(true) + } + + is AuthState.Status -> { + updateStatus(it.status) + } + } + }) + } + + override fun onResume() { + if (::VM.isInitialized) { + VM.observe() + } + super.onResume() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_splash, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_blacklist_manager -> { + open(BlacklistActivity::class.java) + return true + } + R.id.menu_spoof_manager -> { + open(SpoofActivity::class.java) + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.elevation = 0f + actionBar.title = "" + } + } + + override fun onDestroy() { + EventBus.getDefault().unregister(this) + super.onDestroy() + } + + @Subscribe() + fun onEventReceived(event: BusEvent) { + when (event) { + is BusEvent.GoogleAAS -> { + if (event.success) { + updateStatus("Verifying Google Session") + VM.buildGoogleAuthData(event.email, event.aasToken) + } else { + updateStatus("Failed to login via Google") + } + } + else -> { + + } + } + } + + private fun updateStatus(string: String?) { + runOnUiThread { + B.txtStatus.apply { + text = string + } + } + } + + private fun updateActionLayout(isVisible: Boolean) { + if (isVisible) { + B.layoutAction.visibility = View.VISIBLE + } else { + B.layoutAction.visibility = View.INVISIBLE + } + } + + private fun attachActions() { + B.btnAnonymous.addOnClickListener { + B.btnAnonymous.updateProgress(true) + VM.buildAnonymousAuthData() + } + + B.btnGoogle.addOnClickListener { + B.btnGoogle.updateProgress(true) + openGoogleActivity() + } + } + + private fun moveToContent() { + runOnUiThread { open(MainActivity::class.java, true) } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt new file mode 100644 index 000000000..cbe71b64a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt @@ -0,0 +1,120 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.providers.NativeDeviceInfoProvider +import com.aurora.store.data.providers.SpoofDeviceProvider +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.preference.DeviceViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import java.util.* + + +class DeviceSpoofFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var spoofProvider: SpoofProvider + + private var properties: Properties = Properties() + + companion object { + @JvmStatic + fun newInstance(): DeviceSpoofFragment { + return DeviceSpoofFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + properties = NativeDeviceInfoProvider(requireContext()).getNativeDeviceProperties() + spoofProvider = SpoofProvider.with(requireContext()) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + task { + fetchAvailableDevices() + } successUi { + updateController(it) + } failUi { + Log.e("Could not get spoof device properties") + } + } + + private fun updateController(locales: List) { + B.recycler.withModels { + setFilterDuplicates(true) + locales.forEach { + add( + DeviceViewModel_() + .id(it.hashCode()) + .markChecked(properties == it) + .checked { _, checked -> + if (checked) { + properties = it + saveSelection(it) + requestModelBuild() + } + } + .properties(it) + ) + } + } + } + + private fun fetchAvailableDevices(): List { + return SpoofDeviceProvider.with(requireContext()).availableDevice + } + + private fun saveSelection(properties: Properties) { + requireContext().toast(R.string.spoof_apply) + spoofProvider.setSpoofDeviceProperties(properties) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt new file mode 100644 index 000000000..4e211ba59 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt @@ -0,0 +1,121 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.preference.LocaleViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import java.util.* + + +class LocaleSpoofFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var spoofProvider: SpoofProvider + + private var locale: Locale = Locale.getDefault() + + companion object { + @JvmStatic + fun newInstance(): LocaleSpoofFragment { + return LocaleSpoofFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + spoofProvider = SpoofProvider.with(requireContext()) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (spoofProvider.isLocaleSpoofEnabled()) + locale = spoofProvider.getSpoofLocale() + + task { + fetchAvailableLocales() + } successUi { + updateController(it) + } failUi { + Log.e("Could not get available locales") + } + } + + private fun updateController(locales: List) { + B.recycler.withModels { + setFilterDuplicates(true) + locales.forEach { + add( + LocaleViewModel_() + .id(it.language) + .markChecked(locale == it) + .checked { _, checked -> + if (checked) { + locale = it + saveSelection(it) + requestModelBuild() + } + } + .locale(it) + ) + } + } + } + + private fun fetchAvailableLocales(): List { + val locales = Locale.getAvailableLocales() + val localeList: MutableList = ArrayList() + localeList.addAll(locales) + localeList.add(0, Locale.getDefault()) + return localeList + } + + private fun saveSelection(locale: Locale) { + requireContext().toast(R.string.spoof_apply) + spoofProvider.setSpoofLocale(locale) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt new file mode 100644 index 000000000..914a12e37 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt @@ -0,0 +1,92 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.store.R +import com.aurora.store.databinding.ActivityGenericPagerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + +class SpoofActivity : BaseActivity() { + + private lateinit var B: ActivityGenericPagerBinding + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericPagerBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + attachViewPager() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_spoof_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachViewPager() { + B.pager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = getString(R.string.title_device) + 1 -> tab.text = getString(R.string.title_language) + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> DeviceSpoofFragment.newInstance() + 1 -> LocaleSpoofFragment.newInstance() + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 2 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt new file mode 100644 index 000000000..113ba9743 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -0,0 +1,277 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.updates + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.PurchaseHelper +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.downloader.RequestBuilder +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.flushAndAdd +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.AppUpdateViewModel_ +import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_ +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.view.ui.sheets.AppMenuSheet +import com.aurora.store.viewmodel.all.UpdatesViewModel +import com.tonyodev.fetch2.AbstractFetchGroupListener +import com.tonyodev.fetch2.Download +import com.tonyodev.fetch2.Fetch +import com.tonyodev.fetch2.FetchGroup +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi + +class UpdatesFragment : BaseFragment() { + + private lateinit var B: FragmentUpdatesBinding + private lateinit var VM: UpdatesViewModel + + private lateinit var authData: AuthData + private lateinit var purchaseHelper: PurchaseHelper + + private lateinit var fetch: Fetch + private lateinit var fetchListener: AbstractFetchGroupListener + + private val appList: MutableList = mutableListOf() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java) + + authData = AuthProvider.with(requireContext()).getAuthData() + purchaseHelper = PurchaseHelper.with(authData) + + fetch = DownloadManager.with(requireContext()).fetch + fetchListener = object : AbstractFetchGroupListener() { + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + super.onCompleted(groupId, download, fetchGroup) + if (fetchGroup.groupDownloadProgress == 100) { + install(download.tag!!, fetchGroup.downloads) + } + } + } + + fetch.addListener(fetchListener) + + return B.root + } + + override fun onDestroyView() { + if (::fetch.isInitialized && ::fetchListener.isInitialized) { + fetch.removeListener(fetchListener) + } + super.onDestroyView() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + appList.flushAndAdd(it) + updateController(it) + B.swipeRefreshLayout.isRefreshing = false + }) + + B.swipeRefreshLayout.setOnRefreshListener { + VM.observe() + } + + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + if (appList.isEmpty()) { + add( + NoAppViewModel_() + .id("no_update") + .icon(R.drawable.ic_updates) + .message(getString(R.string.details_no_updates)) + ) + } else { + add( + if (VM.selectedUpdates.isEmpty()) { + UpdateHeaderViewModel_() + .id("header_all") + .title("${appList.size} updates available") + .action(getString(R.string.action_update_all)) + .click { v -> + updateAction(false) + } + } else { + UpdateHeaderViewModel_() + .id("header_selected") + .title("${VM.selectedUpdates.size} updates selected") + .action(getString(R.string.action_update)) + .click { v -> + updateAction(true) + } + } + ) + + appList.forEach { app -> + add( + AppUpdateViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + .longClick { _ -> + openAppMenuSheet(app) + false + } + .markChecked(VM.selectedUpdates.contains(app.packageName)) + .checked { _, isChecked -> + if (isChecked) + VM.selectedUpdates.add(app.packageName) + else + VM.selectedUpdates.remove(app.packageName) + + requestModelBuild() + } + ) + } + } + } + } + } + + private fun updateAction(selectedOnly: Boolean) { + if (VM.isUpdating) { + requireContext().toast("Update in progress, let previous batch finish first") + } else { + if (selectedOnly) { + updateSelected() + } else { + updateAll() + } + } + } + + private fun updateAll() { + appList.forEach { app -> + task { + val files = purchaseHelper.purchase(app.packageName, app.versionCode, app.offerType) + files.map { RequestBuilder.buildRequest(requireContext(), app, it) } + } successUi { + val requests = it.filter { request -> request.url.isNotEmpty() }.toList() + if (requests.isNotEmpty()) { + VM.isUpdating = true + fetch.enqueue(requests) { + Log.i("Updating ${app.displayName}") + } + } else { + requireContext().toast("Failed to update ${app.displayName}") + } + } failUi { + Log.e("Failed to update ${app.displayName}") + } + } + } + + private fun updateSelected() { + val selectedPackages = VM.selectedUpdates + appList.forEach { app -> + if (selectedPackages.contains(app.packageName)) { + task { + val files = purchaseHelper.purchase( + app.packageName, + app.versionCode, + app.offerType + ) + files.map { RequestBuilder.buildRequest(requireContext(), app, it) } + } successUi { + val requests = it.filter { request -> request.url.isNotEmpty() }.toList() + if (requests.isNotEmpty()) { + VM.isUpdating = true + fetch.enqueue(requests) { + Log.i("Updating ${app.displayName}") + } + } else { + requireContext().toast("Failed to update ${app.displayName}") + } + } failUi { + Log.e("Failed to update ${app.displayName}") + } + } + } + } + + @Synchronized + private fun install(packageName: String, files: List) { + task { + AppInstaller.with(requireContext()) + .getPreferredInstaller() + .install( + packageName, + files + .filter { it.file.endsWith(".apk") } + .map { it.file }.toList() + ) + } + } + + private fun openAppMenuSheet(app: App) { + val fragment = childFragmentManager.findFragmentByTag(AppMenuSheet.TAG) + if (fragment != null) + childFragmentManager.beginTransaction().remove(fragment) + + AppMenuSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + }.show( + childFragmentManager, + AppMenuSheet.TAG + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt new file mode 100644 index 000000000..b852296ce --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt @@ -0,0 +1,80 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import com.aurora.store.data.RequestState +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.util.Log +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel(application), + NetworkProvider.NetworkListener { + + private lateinit var networkListener: NetworkProvider.NetworkListener + + protected val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC) + .create() + + protected var requestState: RequestState + + init { + Log.i("${javaClass.simpleName} Created") + + requestState = RequestState.Init + + NetworkProvider.addListener(this) + } + + abstract fun observe() + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + redoLastNetworkTask() + } + + private fun redoLastNetworkTask() { + when (requestState) { + RequestState.Pending -> { + observe() + } + else -> { + + } + } + } + + override fun onCleared() { + Log.i("${javaClass.simpleName} Destroyed") + NetworkProvider.removeListener(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt new file mode 100644 index 000000000..b9fd86e96 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt @@ -0,0 +1,93 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import android.content.pm.PackageInfo +import androidx.lifecycle.MutableLiveData +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.util.PackageUtil +import com.aurora.store.util.Preferences +import com.aurora.store.viewmodel.BaseAndroidViewModel +import java.util.* + +abstract class BaseAppsViewModel(application: Application) : BaseAndroidViewModel(application) { + + val authData = AuthProvider + .with(application) + .getAuthData() + + val appDetailsHelper = + AppDetailsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + var blacklistProvider = BlacklistProvider + .with(application) + + val liveData: MutableLiveData> = MutableLiveData() + + var appList: MutableList = mutableListOf() + var packageInfoMap: MutableMap = mutableMapOf() + + fun getFilteredApps(): List { + val blackList = blacklistProvider.getBlackList() + val gapps = getGApps() + + val isGoogleFilterEnabled = Preferences.getBoolean( + getApplication(), + Preferences.PREFERENCE_FILTER_GOOGLE + ) + + packageInfoMap.clear() + packageInfoMap = PackageUtil.getPackageInfoMap(getApplication()) + + packageInfoMap.keys.let { packages -> + /*Filter black list*/ + var filtersPackages = packages + .filter { !blackList.contains(it) } + + /*Filter google apps*/ + if (isGoogleFilterEnabled) + filtersPackages = filtersPackages + .filter { !it.startsWith("com.google") } + .filter { !gapps.contains(it) } + + return appDetailsHelper + .getAppByPackageName(filtersPackages) + .filter { it.displayName.isNotEmpty() } + } + } + + open fun getGApps(): Set { + val gapps: MutableSet = HashSet() + gapps.add("com.chrome.beta") + gapps.add("com.chrome.canary") + gapps.add("com.chrome.dev") + gapps.add("com.android.chrome") + gapps.add("com.niksoftware.snapseed") + gapps.add("com.google.toontastic") + return gapps + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt new file mode 100644 index 000000000..bb3d71b58 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt @@ -0,0 +1,86 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import android.content.pm.PackageManager +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.store.data.RequestState +import com.aurora.store.data.model.Black +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.util.PackageUtil +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class BlacklistViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val packageManager: PackageManager = application.packageManager + private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(application) + + var blackList: MutableList = mutableListOf() + var selected: MutableSet = mutableSetOf() + + val liveData: MutableLiveData> = MutableLiveData() + + init { + requestState = RequestState.Init + selected = blacklistProvider.getBlackList() + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val packageInfoMap = PackageUtil.getPackageInfoMap(getApplication()) + packageInfoMap.values + .filter { + it.packageName != null + && it.versionName != null + && it.applicationInfo != null + && it.applicationInfo.enabled + } + .forEach { + val black = Black(it.packageName).apply { + displayName = packageManager.getApplicationLabel(it.applicationInfo) + .toString() + versionCode = it.versionCode + versionName = it.versionName + drawable = packageManager.getApplicationIcon(packageName) + } + blackList.add(black) + } + liveData.postValue(blackList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } catch (e: Exception) { + e.printStackTrace() + requestState = RequestState.Pending + } + } + } + } + + override fun onCleared() { + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt new file mode 100644 index 000000000..0e43c31ed --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -0,0 +1,88 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.aurora.store.data.RequestState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class InstalledViewModel(application: Application) : BaseAppsViewModel(application) { + + init { + EventBus.getDefault().register(this) + + requestState = RequestState.Init + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + appList.flushAndAdd(getFilteredApps()) + liveData.postValue(appList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onEvent(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.UninstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.Blacklisted -> { + observe() + } + else -> { + + } + } + } + + private fun updateListAndPost(packageName: String) { + //Remove from current list + val updatedList = appList.filter { + it.packageName != packageName + }.toList() + + appList.flushAndAdd(updatedList) + + //Post new update list + liveData.postValue(appList.sortedBy { it.displayName }) + } + + override fun onCleared() { + EventBus.getDefault().unregister(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt new file mode 100644 index 000000000..5cf475a23 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.ClusterHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val clusterHelper: ClusterHelper = + ClusterHelper.with(authData).using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + init { + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + when { + streamCluster.clusterAppList.isEmpty() -> { + val newCluster = + clusterHelper.getCluster(ClusterHelper.Type.MY_APPS_LIBRARY) + updateCluster(newCluster) + liveData.postValue(streamCluster) + } + streamCluster.hasNext() -> { + val newCluster = clusterHelper.next(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(streamCluster) + } + else -> { + requestState = RequestState.Complete + } + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt new file mode 100644 index 000000000..eea21b568 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt @@ -0,0 +1,112 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.RequestState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class UpdatesViewModel(application: Application) : BaseAppsViewModel(application) { + + var selectedUpdates: MutableSet = mutableSetOf() + var isUpdating: Boolean = false + + init { + EventBus.getDefault().register(this) + + requestState = RequestState.Init + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + val marketApps = getFilteredApps() + checkUpdate(marketApps) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + private fun checkUpdate(subAppList: List) { + subAppList.filter { + val packageInfo = packageInfoMap[it.packageName] + if (packageInfo != null) { + it.versionCode > packageInfo.versionCode + } else { + false + } + }.also { apps -> + appList.flushAndAdd(apps) + liveData.postValue(appList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onEvent(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.UninstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.Blacklisted -> { + updateListAndPost(event.packageName) + } + else -> { + + } + } + } + + private fun updateListAndPost(packageName: String) { + //Remove from selected updates + selectedUpdates.remove(packageName) + + if (selectedUpdates.isEmpty()) + isUpdating = false + + //Remove from current update list + val updatedList = appList.filter { + it.packageName != packageName + }.toList() + + appList.flushAndAdd(updatedList) + + //Post new update list + liveData.postValue(appList.sortedBy { it.displayName }) + } + + override fun onCleared() { + EventBus.getDefault().unregister(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt new file mode 100644 index 000000000..72f603282 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt @@ -0,0 +1,224 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.auth + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.Constants +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AuthHelper +import com.aurora.gplayapi.helpers.AuthValidator +import com.aurora.store.AccountType +import com.aurora.store.data.AuthState +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AccountProvider +import com.aurora.store.data.providers.NativeDeviceInfoProvider +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import nl.komponents.kovenant.task +import java.net.ConnectException +import java.net.UnknownHostException + +class AuthViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val spoofProvider = SpoofProvider.with(getApplication()) + + val liveData: MutableLiveData = MutableLiveData() + + init { + requestState = RequestState.Init + } + + override fun observe() { + val signedIn = Preferences.getBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN) + if (signedIn) { + liveData.postValue(AuthState.Available) + buildSavedAuthData() + } else { + liveData.postValue(AuthState.Unavailable) + } + } + + fun buildGoogleAuthData(email: String, aasToken: String) { + updateStatus("Requesting new session") + + task { + var properties = NativeDeviceInfoProvider(getApplication()).getNativeDeviceProperties() + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + return@task AuthHelper.build(email, aasToken, properties) + } success { + verifyAndSaveAuth(it, AccountType.GOOGLE) + } fail { + updateStatus("Failed to generate Session") + } + } + + fun buildAnonymousAuthData() { + updateStatus("Requesting new session") + + task { + var properties = NativeDeviceInfoProvider(getApplication()) + .getNativeDeviceProperties() + + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + val playResponse = HttpClient + .getPreferredClient() + .postAuth( + Constants.URL_DISPENSER, + gson.toJson(properties).toByteArray() + ) + + if (playResponse.isSuccessful) { + return@task gson.fromJson( + String(playResponse.responseBytes), + AuthData::class.java + ) + } else { + when (playResponse.code) { + 404 -> throw Exception("Server unreachable") + 429 -> throw Exception("Oops, You are rate limited") + else -> throw Exception(playResponse.errorString) + } + } + } success { + //Set AuthData as anonymous + it.isAnonymous = true + verifyAndSaveAuth(it, AccountType.ANONYMOUS) + } fail { + updateStatus(it.message.toString()) + } + } + + private fun buildSavedAuthData() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + //Load & validate saved AuthData + val savedAuthData = getSavedAuthData() + + if (isValid(savedAuthData)) { + liveData.postValue(AuthState.Valid) + requestState = RequestState.Complete + } else { + //Generate and validate new auth + val type = AccountProvider.with(getApplication()).getAccountType() + when (type) { + AccountType.GOOGLE -> { + val email = Preferences.getString( + getApplication(), + Constants.ACCOUNT_EMAIL_PLAIN + ) + val aasToken = Preferences.getString( + getApplication(), + Constants.ACCOUNT_AAS_PLAIN + ) + buildGoogleAuthData(email, aasToken) + } + AccountType.ANONYMOUS -> { + buildAnonymousAuthData() + } + } + } + } catch (e: Exception) { + when (e) { + is UnknownHostException -> updateStatus("No network") + is ConnectException -> updateStatus("Could not connect to server") + else -> updateStatus("Unknown error") + } + requestState = RequestState.Pending + } + } + } + } + + private fun getSavedAuthData(): AuthData { + val rawAuth: String = Preferences.getString(getApplication(), PREFERENCE_AUTH_DATA) + return if (rawAuth.isNotBlank()) + gson.fromJson(rawAuth, AuthData::class.java) + else + AuthData("", "") + } + + private fun isValid(authData: AuthData): Boolean { + return try { + AuthValidator.with(authData) + .using(HttpClient.getPreferredClient()) + .isValid() + } catch (e: Exception) { + false + } + } + + private fun verifyAndSaveAuth(authData: AuthData, type: AccountType) { + updateStatus("Verifying new session") + + if (spoofProvider.isLocaleSpoofEnabled()) { + authData.locale = spoofProvider.getSpoofLocale() + } + + if (authData.authToken.isNotEmpty() && authData.deviceConfigToken.isNotEmpty()) { + configAuthPref(authData, type, true) + liveData.postValue(AuthState.SignedIn) + requestState = RequestState.Complete + } else { + configAuthPref(authData, type, false) + liveData.postValue(AuthState.SignedOut) + requestState = RequestState.Pending + + updateStatus("Failed to verify session") + } + } + + private fun configAuthPref(authData: AuthData, type: AccountType, signedIn: Boolean) { + if (signedIn) { + //Save Auth Data + Preferences.putString( + getApplication(), + PREFERENCE_AUTH_DATA, + gson.toJson(authData) + ) + } + + //Save Auth Type + Preferences.putString( + getApplication(), + Constants.ACCOUNT_TYPE, + type.name // ANONYMOUS OR GOOGLE + ) + + //Save Auth Status + Preferences.putBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN, signedIn) + } + + private fun updateStatus(status: String) { + liveData.postValue(AuthState.Status(status)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt new file mode 100644 index 000000000..6f8dfc8d3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.browse + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + fun getStreamBundle(browseUrl: String) { + viewModelScope.launch(Dispatchers.IO) { + liveData.postValue(getInitialCluster(browseUrl)) + } + } + + private fun getInitialCluster( + browseUrl: String + ): StreamCluster { + + requestState = RequestState.Init + + val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl) + + if (browseResponse.contentsUrl.isNotEmpty()) + streamCluster = streamHelper.getNextStreamCluster(browseResponse.contentsUrl) + else if (browseResponse.hasBrowseTab()) + streamCluster = streamHelper.getNextStreamCluster(browseResponse.browseTab.listUrl) + + streamCluster.apply { + clusterTitle = browseResponse.title + } + + return streamCluster + } + + fun nextCluster() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = streamHelper.getNextStreamCluster( + streamCluster.clusterNextPageUrl + ) + + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + + liveData.postValue(streamCluster) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + override fun observe() { + requestState = RequestState.Init + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt new file mode 100644 index 000000000..d03ad4e70 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt @@ -0,0 +1,59 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.category + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.Category +import com.aurora.gplayapi.helpers.CategoryHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +abstract class BaseCategoryViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val streamHelper: CategoryHelper = CategoryHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData> = MutableLiveData() + + lateinit var type: Category.Type + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + liveData.postValue(streamHelper.getAllCategoriesList(type)) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt new file mode 100644 index 000000000..00bdb0205 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt @@ -0,0 +1,42 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.category + +import android.app.Application +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.data.RequestState + +class AppCategoryViewModel(application: Application) : BaseCategoryViewModel(application) { + + init { + type = Category.Type.APPLICATION + requestState = RequestState.Init + observe() + } +} + +class GameCategoryViewModel(application: Application) : BaseCategoryViewModel(application) { + + init { + type = Category.Type.GAME + requestState = RequestState.Init + observe() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt new file mode 100644 index 000000000..bd744cb3d --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt @@ -0,0 +1,63 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.editorschoice + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +open class BaseEditorChoiceViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + lateinit var category: StreamHelper.Category + + val liveData: MutableLiveData> = MutableLiveData() + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val editorChoiceBundle = streamHelper.getEditorChoiceStream(category) + liveData.postValue(editorChoiceBundle) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt new file mode 100644 index 000000000..695657d21 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.editorschoice + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper + +class AppEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) { + init { + category = StreamHelper.Category.APPLICATION + observe() + } +} + + +class GameEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) { + init { + category = StreamHelper.Category.GAME + observe() + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt new file mode 100644 index 000000000..b70349892 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +abstract class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider.with(application).getAuthData() + var streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamBundle: StreamBundle = StreamBundle() + + lateinit var type: StreamHelper.Type + lateinit var category: StreamHelper.Category + + open fun getStreamBundle( + nextPageUrl: String, + category: StreamHelper.Category, + type: StreamHelper.Type + ): StreamBundle { + return if (streamBundle.streamClusters.isEmpty()) + streamHelper.getNavStream(type, category) + else + streamHelper.next(nextPageUrl) + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (!streamBundle.hasCluster() || streamBundle.hasNext()) { + + //Fetch new stream bundle + val newBundle = getStreamBundle( + streamBundle.streamNextPageUrl, + category, + type + ) + + //Update old bundle + streamBundle.apply { + streamClusters.putAll(newBundle.streamClusters) + streamNextPageUrl = newBundle.streamNextPageUrl + } + + //Post updated to UI + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + fun observeCluster(streamCluster: StreamCluster) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = + streamHelper.getNextStreamCluster(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of cluster") + streamCluster.clusterNextPageUrl = String() + } + } catch (e: Exception) { + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamBundle.streamClusters[newCluster.id]?.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt new file mode 100644 index 000000000..162112b53 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt @@ -0,0 +1,45 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.ViewState + +class EarlyAccessAppsViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.APPLICATION + type = StreamHelper.Type.EARLY_ACCESS + liveData.postValue(ViewState.Loading) + observe() + } +} + +class EarlyAccessGamesViewModel(application: Application) : + BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.GAME + type = StreamHelper.Type.EARLY_ACCESS + liveData.postValue(ViewState.Loading) + observe() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt new file mode 100644 index 000000000..2c1ef62f8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt @@ -0,0 +1,44 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.ViewState + +class AppsForYouViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.APPLICATION + type = StreamHelper.Type.HOME + liveData.postValue(ViewState.Loading) + observe() + } +} + +class GamesForYouViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.GAME + type = StreamHelper.Type.HOME + liveData.postValue(ViewState.Loading) + observe() + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt new file mode 100644 index 000000000..280f4d676 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt @@ -0,0 +1,75 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.review + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.Review +import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class ReviewViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + var reviewsHelper: ReviewsHelper = ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + + private val reviews: MutableList = mutableListOf() + private var offset: Int = 0 + + fun fetchReview(packageName: String, filter: Review.Filter) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val newReviews = reviewsHelper.getReviews(packageName, filter, offset) + reviews.addAll(newReviews) + offset = reviews.size + liveData.postValue(ViewState.Success(reviews)) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + fun reset() { + reviews.clear() + offset = 0 + } + + override fun observe() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt new file mode 100644 index 000000000..9505fe3a1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.sale + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AppSalesHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class AppSalesViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val appSalesHelper: AppSalesHelper = + AppSalesHelper.with(authData).using(HttpClient.getPreferredClient()) + + val liveAppList: MutableLiveData> = MutableLiveData() + + private var page: Int = 0 + private val appList: MutableList = mutableListOf() + + init { + observeAppSales() + } + + private fun observeAppSales() { + appList.clear() + viewModelScope.launch(Dispatchers.IO) { + appList.addAll(getSearchResults()) + liveAppList.postValue(appList) + } + } + + private fun getSearchResults( + ): List { + return appSalesHelper.getAppsOnSale(page = page++, offer = 100) + } + + fun next() { + viewModelScope.launch(Dispatchers.IO) { + val newAppList = getSearchResults() + if (newAppList.isNotEmpty()) { + appList.addAll(getSearchResults()) + liveAppList.postValue(appList) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt new file mode 100644 index 000000000..8e01f4550 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt @@ -0,0 +1,95 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.gplayapi.helpers.SearchHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class SearchResultViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val searchHelper: SearchHelper = SearchHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + + private var searchBundle: SearchBundle = SearchBundle() + + fun observeSearchResults(query: String) { + //Clear old results + searchBundle.subBundles.clear() + searchBundle.appList.clear() + //Fetch new results + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + searchBundle = search(query) + liveData.postValue(searchBundle) + } catch (e: Exception) { + + } + } + } + } + + private fun search( + query: String + ): SearchBundle { + return searchHelper.searchResults(query) + } + + @Synchronized + fun next(nextSubBundleSet: MutableSet) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (nextSubBundleSet.isNotEmpty()) { + val newSearchBundle = searchHelper.next(nextSubBundleSet) + if (newSearchBundle.appList.isNotEmpty()) { + searchBundle.apply { + subBundles.flushAndAdd(newSearchBundle.subBundles) + appList.addAll(newSearchBundle.appList) + } + + liveData.postValue(searchBundle) + } + } + } catch (e: Exception) { + + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt new file mode 100644 index 000000000..374567175 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt @@ -0,0 +1,57 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.SearchHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class SearchSuggestionViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val searchHelper: SearchHelper = SearchHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveSearchSuggestions: MutableLiveData> = MutableLiveData() + + fun observeStreamBundles(query: String) { + viewModelScope.launch(Dispatchers.IO) { + liveSearchSuggestions.postValue(getSearchSuggestions(query)) + } + } + + private fun getSearchSuggestions( + query: String + ): List { + return searchHelper.searchSuggestions(query) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt new file mode 100644 index 000000000..cb76de896 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.subcategory + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.CategoryHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider.with(application).getAuthData() + var categoryHelper: CategoryHelper = CategoryHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamBundle: StreamBundle = StreamBundle() + + lateinit var homeUrl: String + + init { + liveData.postValue(ViewState.Loading) + } + + private fun getCategoryStreamBundle( + nextPageUrl: String + ): StreamBundle { + return if (streamBundle.streamClusters.isEmpty()) + categoryHelper.getSubCategoryBundle(homeUrl) + else + categoryHelper.getSubCategoryBundle(nextPageUrl) + } + + fun observeCategory(homeUrl: String) { + this.homeUrl = homeUrl + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (!streamBundle.hasCluster() || streamBundle.hasNext()) { + //Fetch new stream bundle + val newBundle = getCategoryStreamBundle( + streamBundle.streamNextPageUrl + ) + + //Update old bundle + streamBundle.apply { + streamClusters.putAll(newBundle.streamClusters) + streamNextPageUrl = newBundle.streamNextPageUrl + } + + //Post updated to UI + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + e.printStackTrace() + requestState = RequestState.Pending + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + fun observeCluster(streamCluster: StreamCluster) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = + categoryHelper.getNextStreamCluster(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of cluster") + streamCluster.clusterNextPageUrl = String() + } + } catch (e: Exception) { + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamBundle.streamClusters[newCluster.id]?.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt new file mode 100644 index 000000000..f4793556b --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import com.aurora.gplayapi.helpers.TopChartsHelper + +class TopFreeAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_SELLING_FREE + observe() + } +} + +class TopGrossingAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_GROSSING + observe() + } +} + +class TrendingAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.MOVERS_SHAKERS + observe() + } +} + +class TopPaidAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_SELLING_PAID + observe() + } +} + diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt new file mode 100644 index 000000000..cca4e9c2f --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.TopChartsHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +abstract class BaseChartViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val topChartsHelper: TopChartsHelper = + TopChartsHelper.with(authData).using(HttpClient.getPreferredClient()) + + lateinit var type: TopChartsHelper.Type + lateinit var chart: TopChartsHelper.Chart + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + fun getStreamCluster( + type: TopChartsHelper.Type, + chart: TopChartsHelper.Chart + ): StreamCluster { + return topChartsHelper.getCluster(type, chart) + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + streamCluster = getStreamCluster(type, chart) + liveData.postValue(streamCluster) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + fun nextCluster() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = topChartsHelper.getNextStreamCluster( + streamCluster.clusterNextPageUrl + ) + + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + + liveData.postValue(streamCluster) + } else { + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt new file mode 100644 index 000000000..0b819252a --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import com.aurora.gplayapi.helpers.TopChartsHelper + +class TopFreeGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_SELLING_FREE + observe() + } +} + +class TopGrossingGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_GROSSING + observe() + } +} + +class TrendingGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.MOVERS_SHAKERS + observe() + } +} + +class TopPaidGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_SELLING_PAID + observe() + } +} + diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 000000000..2023c0781 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 000000000..408a3c18d --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/color-v21/chip_txt_selector.xml b/app/src/main/res/color-v21/chip_txt_selector.xml new file mode 100644 index 000000000..672bff308 --- /dev/null +++ b/app/src/main/res/color-v21/chip_txt_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_stroke_selector.xml b/app/src/main/res/color/chip_stroke_selector.xml new file mode 100644 index 000000000..3c20a1261 --- /dev/null +++ b/app/src/main/res/color/chip_stroke_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_surface_selector.xml b/app/src/main/res/color/chip_surface_selector.xml new file mode 100644 index 000000000..ac3d3d5f6 --- /dev/null +++ b/app/src/main/res/color/chip_surface_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_txt_selector.xml b/app/src/main/res/color/chip_txt_selector.xml new file mode 100644 index 000000000..0f1477cdc --- /dev/null +++ b/app/src/main/res/color/chip_txt_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_bottomsheet.xml b/app/src/main/res/drawable-v21/bg_bottomsheet.xml new file mode 100644 index 000000000..277d05c23 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_bottomsheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_outlined_padded.xml b/app/src/main/res/drawable-v21/bg_outlined_padded.xml new file mode 100644 index 000000000..5b14e0853 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_outlined_padded.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_placeholder.xml b/app/src/main/res/drawable-v21/bg_placeholder.xml new file mode 100644 index 000000000..0730630a8 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_placeholder.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable-v21/bg_rounded.xml b/app/src/main/res/drawable-v21/bg_rounded.xml new file mode 100644 index 000000000..b90a90041 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_rounded.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable-v21/bg_search.xml b/app/src/main/res/drawable-v21/bg_search.xml new file mode 100644 index 000000000..c66a75909 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_search.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_sheet.xml b/app/src/main/res/drawable-v21/bg_sheet.xml new file mode 100644 index 000000000..c1bc5fbcd --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_sheet.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider.xml b/app/src/main/res/drawable-v21/divider.xml new file mode 100644 index 000000000..084d1feba --- /dev/null +++ b/app/src/main/res/drawable-v21/divider.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider_alt.xml b/app/src/main/res/drawable-v21/divider_alt.xml new file mode 100644 index 000000000..386cb0ec6 --- /dev/null +++ b/app/src/main/res/drawable-v21/divider_alt.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider_line.xml b/app/src/main/res/drawable-v21/divider_line.xml new file mode 100644 index 000000000..1211d8fba --- /dev/null +++ b/app/src/main/res/drawable-v21/divider_line.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_cancel.xml b/app/src/main/res/drawable-v21/ic_cancel.xml new file mode 100644 index 000000000..fc5191f65 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_cancel.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/drawable-v21/ic_shield.xml b/app/src/main/res/drawable-v21/ic_shield.xml new file mode 100644 index 000000000..bc8ccccbc --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_shield.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable-v21/tab_default_onboarding.xml b/app/src/main/res/drawable-v21/tab_default_onboarding.xml new file mode 100644 index 000000000..c8f8e6fb6 --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_default_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_indicator.xml b/app/src/main/res/drawable-v21/tab_indicator.xml new file mode 100644 index 000000000..9b7a64f9c --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_indicator.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_selected_onboarding.xml b/app/src/main/res/drawable-v21/tab_selected_onboarding.xml new file mode 100644 index 000000000..929f249f6 --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_selected_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_selector_onboarding.xml b/app/src/main/res/drawable-v21/tab_selector_onboarding.xml new file mode 100644 index 000000000..e4e61187f --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_selector_onboarding.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/bg_outlined_rounded.xml b/app/src/main/res/drawable-v24/bg_outlined_rounded.xml new file mode 100644 index 000000000..18a82ccd1 --- /dev/null +++ b/app/src/main/res/drawable-v24/bg_outlined_rounded.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/bg_progressbar.xml b/app/src/main/res/drawable-v24/bg_progressbar.xml new file mode 100644 index 000000000..836dd351b --- /dev/null +++ b/app/src/main/res/drawable-v24/bg_progressbar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_arrow_left.xml b/app/src/main/res/drawable-v24/ic_arrow_left.xml new file mode 100644 index 000000000..424dcfaf0 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_arrow_left.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..1957cd575 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_bottomsheet.xml b/app/src/main/res/drawable/bg_bottomsheet.xml new file mode 100644 index 000000000..04b17158c --- /dev/null +++ b/app/src/main/res/drawable/bg_bottomsheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_cancel.xml b/app/src/main/res/drawable/bg_cancel.xml new file mode 100644 index 000000000..c5d625d38 --- /dev/null +++ b/app/src/main/res/drawable/bg_cancel.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_changelog.xml b/app/src/main/res/drawable/bg_changelog.xml new file mode 100644 index 000000000..03585a476 --- /dev/null +++ b/app/src/main/res/drawable/bg_changelog.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/bg_circle.xml b/app/src/main/res/drawable/bg_circle.xml new file mode 100644 index 000000000..ab74b3dcb --- /dev/null +++ b/app/src/main/res/drawable/bg_circle.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_gradient_linear.xml b/app/src/main/res/drawable/bg_gradient_linear.xml new file mode 100644 index 000000000..08e67c70f --- /dev/null +++ b/app/src/main/res/drawable/bg_gradient_linear.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_outlined_padded.xml b/app/src/main/res/drawable/bg_outlined_padded.xml new file mode 100644 index 000000000..ddc12705f --- /dev/null +++ b/app/src/main/res/drawable/bg_outlined_padded.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_outlined_rounded.xml b/app/src/main/res/drawable/bg_outlined_rounded.xml new file mode 100644 index 000000000..18a82ccd1 --- /dev/null +++ b/app/src/main/res/drawable/bg_outlined_rounded.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_placeholder.xml b/app/src/main/res/drawable/bg_placeholder.xml new file mode 100644 index 000000000..87a42b0b8 --- /dev/null +++ b/app/src/main/res/drawable/bg_placeholder.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_progressbar.xml b/app/src/main/res/drawable/bg_progressbar.xml new file mode 100644 index 000000000..8cfaa0b3b --- /dev/null +++ b/app/src/main/res/drawable/bg_progressbar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_round_solid.xml b/app/src/main/res/drawable/bg_round_solid.xml new file mode 100644 index 000000000..45e4fbb09 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_solid.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_rounded.xml b/app/src/main/res/drawable/bg_rounded.xml new file mode 100644 index 000000000..302e6703c --- /dev/null +++ b/app/src/main/res/drawable/bg_rounded.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_rounded_outlined.xml b/app/src/main/res/drawable/bg_rounded_outlined.xml new file mode 100644 index 000000000..8f5073d55 --- /dev/null +++ b/app/src/main/res/drawable/bg_rounded_outlined.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_search.xml b/app/src/main/res/drawable/bg_search.xml new file mode 100644 index 000000000..68150025f --- /dev/null +++ b/app/src/main/res/drawable/bg_search.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_sharp.xml b/app/src/main/res/drawable/bg_sharp.xml new file mode 100644 index 000000000..2705eb864 --- /dev/null +++ b/app/src/main/res/drawable/bg_sharp.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_sheet.xml b/app/src/main/res/drawable/bg_sheet.xml new file mode 100644 index 000000000..b4fdb28e0 --- /dev/null +++ b/app/src/main/res/drawable/bg_sheet.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_ripple_circular_solid.xml b/app/src/main/res/drawable/custom_ripple_circular_solid.xml new file mode 100644 index 000000000..5a8f1c391 --- /dev/null +++ b/app/src/main/res/drawable/custom_ripple_circular_solid.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_ripple_rounded_solid.xml b/app/src/main/res/drawable/custom_ripple_rounded_solid.xml new file mode 100644 index 000000000..538796651 --- /dev/null +++ b/app/src/main/res/drawable/custom_ripple_rounded_solid.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml new file mode 100644 index 000000000..084d1feba --- /dev/null +++ b/app/src/main/res/drawable/divider.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider_alt.xml b/app/src/main/res/drawable/divider_alt.xml new file mode 100644 index 000000000..386cb0ec6 --- /dev/null +++ b/app/src/main/res/drawable/divider_alt.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider_line.xml b/app/src/main/res/drawable/divider_line.xml new file mode 100644 index 000000000..ef81d3268 --- /dev/null +++ b/app/src/main/res/drawable/divider_line.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_manager.xml b/app/src/main/res/drawable/ic_account_manager.xml new file mode 100644 index 000000000..30d46e6d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_manager.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_anonymous.xml b/app/src/main/res/drawable/ic_anonymous.xml new file mode 100644 index 000000000..778348291 --- /dev/null +++ b/app/src/main/res/drawable/ic_anonymous.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_apps.xml b/app/src/main/res/drawable/ic_apps.xml new file mode 100644 index 000000000..2a94e7494 --- /dev/null +++ b/app/src/main/res/drawable/ic_apps.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 000000000..735fcbe1d --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_download.xml b/app/src/main/res/drawable/ic_arrow_download.xml new file mode 100644 index 000000000..61f170a41 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_download.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 000000000..424dcfaf0 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 000000000..5d168b129 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_bhim.xml b/app/src/main/res/drawable/ic_bhim.xml new file mode 100644 index 000000000..d0d87acf1 --- /dev/null +++ b/app/src/main/res/drawable/ic_bhim.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_bitcoin_bch.xml b/app/src/main/res/drawable/ic_bitcoin_bch.xml new file mode 100644 index 000000000..c7a393477 --- /dev/null +++ b/app/src/main/res/drawable/ic_bitcoin_bch.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_bitcoin_btc.xml b/app/src/main/res/drawable/ic_bitcoin_btc.xml new file mode 100644 index 000000000..a66301a7d --- /dev/null +++ b/app/src/main/res/drawable/ic_bitcoin_btc.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_blacklist.xml b/app/src/main/res/drawable/ic_blacklist.xml new file mode 100644 index 000000000..fc72c8639 --- /dev/null +++ b/app/src/main/res/drawable/ic_blacklist.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 000000000..fc5191f65 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 000000000..9f64100ec --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_child.xml b/app/src/main/res/drawable/ic_child.xml new file mode 100644 index 000000000..7ae653fb0 --- /dev/null +++ b/app/src/main/res/drawable/ic_child.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml new file mode 100644 index 000000000..cfa80ff90 --- /dev/null +++ b/app/src/main/res/drawable/ic_code.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 000000000..b3768f763 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_disclaimer.xml b/app/src/main/res/drawable/ic_disclaimer.xml new file mode 100644 index 000000000..0a69e4999 --- /dev/null +++ b/app/src/main/res/drawable/ic_disclaimer.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_disk.xml b/app/src/main/res/drawable/ic_disk.xml new file mode 100644 index 000000000..e511eb188 --- /dev/null +++ b/app/src/main/res/drawable/ic_disk.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 000000000..3bd89f1cd --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download_cancel.png b/app/src/main/res/drawable/ic_download_cancel.png new file mode 100644 index 000000000..c7e27e458 Binary files /dev/null and b/app/src/main/res/drawable/ic_download_cancel.png differ diff --git a/app/src/main/res/drawable/ic_download_fail.png b/app/src/main/res/drawable/ic_download_fail.png new file mode 100644 index 000000000..799de758c Binary files /dev/null and b/app/src/main/res/drawable/ic_download_fail.png differ diff --git a/app/src/main/res/drawable/ic_download_manager.xml b/app/src/main/res/drawable/ic_download_manager.xml new file mode 100644 index 000000000..1ee8c3bfb --- /dev/null +++ b/app/src/main/res/drawable/ic_download_manager.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download_pause.png b/app/src/main/res/drawable/ic_download_pause.png new file mode 100644 index 000000000..c15c5ef14 Binary files /dev/null and b/app/src/main/res/drawable/ic_download_pause.png differ diff --git a/app/src/main/res/drawable/ic_ethereum_eth.xml b/app/src/main/res/drawable/ic_ethereum_eth.xml new file mode 100644 index 000000000..c3c463484 --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereum_eth.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_expand.xml b/app/src/main/res/drawable/ic_expand.xml new file mode 100644 index 000000000..ebdd652f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_faq.xml b/app/src/main/res/drawable/ic_faq.xml new file mode 100644 index 000000000..586c8aa1d --- /dev/null +++ b/app/src/main/res/drawable/ic_faq.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_fdroid.xml b/app/src/main/res/drawable/ic_fdroid.xml new file mode 100644 index 000000000..e2e6b4cb7 --- /dev/null +++ b/app/src/main/res/drawable/ic_fdroid.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 000000000..f9b7f4e0c --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_games.xml b/app/src/main/res/drawable/ic_games.xml new file mode 100644 index 000000000..10158d457 --- /dev/null +++ b/app/src/main/res/drawable/ic_games.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_gitlab.xml b/app/src/main/res/drawable/ic_gitlab.xml new file mode 100644 index 000000000..8ece91164 --- /dev/null +++ b/app/src/main/res/drawable/ic_gitlab.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml new file mode 100644 index 000000000..25a6468f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_google.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 000000000..76f62b81a --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_installation.xml b/app/src/main/res/drawable/ic_installation.xml new file mode 100644 index 000000000..6dee6b603 --- /dev/null +++ b/app/src/main/res/drawable/ic_installation.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..200823d5f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..97be87666 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_libera_pay.xml b/app/src/main/res/drawable/ic_libera_pay.xml new file mode 100644 index 000000000..f3f32d693 --- /dev/null +++ b/app/src/main/res/drawable/ic_libera_pay.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml new file mode 100644 index 000000000..2b7b0028c --- /dev/null +++ b/app/src/main/res/drawable/ic_license.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml new file mode 100644 index 000000000..edc14a840 --- /dev/null +++ b/app/src/main/res/drawable/ic_logo.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 000000000..4066409bc --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_mail.xml b/app/src/main/res/drawable/ic_mail.xml new file mode 100644 index 000000000..68ebd56ab --- /dev/null +++ b/app/src/main/res/drawable/ic_mail.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml new file mode 100644 index 000000000..e6b08e1af --- /dev/null +++ b/app/src/main/res/drawable/ic_map.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_map_marker.xml b/app/src/main/res/drawable/ic_map_marker.xml new file mode 100644 index 000000000..dfb9ed5cc --- /dev/null +++ b/app/src/main/res/drawable/ic_map_marker.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_about.xml b/app/src/main/res/drawable/ic_menu_about.xml new file mode 100644 index 000000000..27c90d4b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_about.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_settings.xml b/app/src/main/res/drawable/ic_menu_settings.xml new file mode 100644 index 000000000..93cdcf041 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_settings.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_unfold.xml b/app/src/main/res/drawable/ic_menu_unfold.xml new file mode 100644 index 000000000..b0ebfab76 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_unfold.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_network.xml b/app/src/main/res/drawable/ic_network.xml new file mode 100644 index 000000000..10503ce41 --- /dev/null +++ b/app/src/main/res/drawable/ic_network.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_notification_outlined.xml b/app/src/main/res/drawable/ic_notification_outlined.xml new file mode 100644 index 000000000..d0d20dd56 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_outlined.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 000000000..bf3240f40 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_paypal.xml b/app/src/main/res/drawable/ic_paypal.xml new file mode 100644 index 000000000..590ecc0f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_paypal.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_privacy.xml b/app/src/main/res/drawable/ic_privacy.xml new file mode 100644 index 000000000..fb7c6dd99 --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_round_search.xml b/app/src/main/res/drawable/ic_round_search.xml new file mode 100644 index 000000000..1e1f8fb8d --- /dev/null +++ b/app/src/main/res/drawable/ic_round_search.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_sale.xml b/app/src/main/res/drawable/ic_sale.xml new file mode 100644 index 000000000..b20cefa30 --- /dev/null +++ b/app/src/main/res/drawable/ic_sale.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_append.xml b/app/src/main/res/drawable/ic_search_append.xml new file mode 100644 index 000000000..8d6dcdbb9 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_append.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_suggestion.xml b/app/src/main/res/drawable/ic_search_suggestion.xml new file mode 100644 index 000000000..f6aa97dd4 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_suggestion.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_send.xml b/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 000000000..aa9d0e168 --- /dev/null +++ b/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 000000000..33b3d3dc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 000000000..bc8ccccbc --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_size.xml b/app/src/main/res/drawable/ic_size.xml new file mode 100644 index 000000000..1ebe7df2b --- /dev/null +++ b/app/src/main/res/drawable/ic_size.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 000000000..09f84fe62 --- /dev/null +++ b/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml new file mode 100644 index 000000000..2fa18d9e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_telegram.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ui.xml b/app/src/main/res/drawable/ic_ui.xml new file mode 100644 index 000000000..306275a54 --- /dev/null +++ b/app/src/main/res/drawable/ic_ui.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_updates.xml b/app/src/main/res/drawable/ic_updates.xml new file mode 100644 index 000000000..a2b6eafa3 --- /dev/null +++ b/app/src/main/res/drawable/ic_updates.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_xda.xml b/app/src/main/res/drawable/ic_xda.xml new file mode 100644 index 000000000..cbf6ad525 --- /dev/null +++ b/app/src/main/res/drawable/ic_xda.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/messages.xml b/app/src/main/res/drawable/messages.xml new file mode 100644 index 000000000..db7cc45d8 --- /dev/null +++ b/app/src/main/res/drawable/messages.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/progressbar_bg.xml b/app/src/main/res/drawable/progressbar_bg.xml new file mode 100644 index 000000000..ef4508896 --- /dev/null +++ b/app/src/main/res/drawable/progressbar_bg.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sync.xml b/app/src/main/res/drawable/sync.xml new file mode 100644 index 000000000..88c1d803b --- /dev/null +++ b/app/src/main/res/drawable/sync.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/tab_default_onboarding.xml b/app/src/main/res/drawable/tab_default_onboarding.xml new file mode 100644 index 000000000..0dfeea04f --- /dev/null +++ b/app/src/main/res/drawable/tab_default_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml new file mode 100644 index 000000000..b38314d05 --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selected_onboarding.xml b/app/src/main/res/drawable/tab_selected_onboarding.xml new file mode 100644 index 000000000..241c10e29 --- /dev/null +++ b/app/src/main/res/drawable/tab_selected_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selector_onboarding.xml b/app/src/main/res/drawable/tab_selector_onboarding.xml new file mode 100644 index 000000000..e4e61187f --- /dev/null +++ b/app/src/main/res/drawable/tab_selector_onboarding.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/gilroy_extra_bold.otf b/app/src/main/res/font/gilroy_extra_bold.otf new file mode 100644 index 000000000..578826317 Binary files /dev/null and b/app/src/main/res/font/gilroy_extra_bold.otf differ diff --git a/app/src/main/res/font/montserrat_regular.otf b/app/src/main/res/font/montserrat_regular.otf new file mode 100644 index 000000000..c1d1ee37b Binary files /dev/null and b/app/src/main/res/font/montserrat_regular.otf differ diff --git a/app/src/main/res/layout-land/activity_about.xml b/app/src/main/res/layout-land/activity_about.xml new file mode 100644 index 000000000..ec1178fda --- /dev/null +++ b/app/src/main/res/layout-land/activity_about.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/activity_account.xml b/app/src/main/res/layout-land/activity_account.xml new file mode 100644 index 000000000..46c7f0c15 --- /dev/null +++ b/app/src/main/res/layout-land/activity_account.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/activity_main.xml b/app/src/main/res/layout-v21/activity_main.xml new file mode 100644 index 000000000..643235d9e --- /dev/null +++ b/app/src/main/res/layout-v21/activity_main.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/item_preference.xml b/app/src/main/res/layout-v21/item_preference.xml new file mode 100644 index 000000000..74ea3d3b3 --- /dev/null +++ b/app/src/main/res/layout-v21/item_preference.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/layout_details_install.xml b/app/src/main/res/layout-v21/layout_details_install.xml new file mode 100644 index 000000000..0aacb6fa9 --- /dev/null +++ b/app/src/main/res/layout-v21/layout_details_install.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/sheet_base.xml b/app/src/main/res/layout-v21/sheet_base.xml new file mode 100644 index 000000000..79cbf1e37 --- /dev/null +++ b/app/src/main/res/layout-v21/sheet_base.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_app.xml b/app/src/main/res/layout-v21/view_app.xml new file mode 100644 index 000000000..b0024a6cd --- /dev/null +++ b/app/src/main/res/layout-v21/view_app.xml @@ -0,0 +1,58 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_dash.xml b/app/src/main/res/layout-v21/view_dash.xml new file mode 100644 index 000000000..e23b19c4f --- /dev/null +++ b/app/src/main/res/layout-v21/view_dash.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_dev_info.xml b/app/src/main/res/layout-v21/view_dev_info.xml new file mode 100644 index 000000000..88e0401b1 --- /dev/null +++ b/app/src/main/res/layout-v21/view_dev_info.xml @@ -0,0 +1,60 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 000000000..27030b04c --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml new file mode 100644 index 000000000..118dd56ea --- /dev/null +++ b/app/src/main/res/layout/activity_account.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml new file mode 100644 index 000000000..bcc07f6ca --- /dev/null +++ b/app/src/main/res/layout/activity_details.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details_more.xml b/app/src/main/res/layout/activity_details_more.xml new file mode 100644 index 000000000..ceffb75dc --- /dev/null +++ b/app/src/main/res/layout/activity_details_more.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details_review.xml b/app/src/main/res/layout/activity_details_review.xml new file mode 100644 index 000000000..3b5d60278 --- /dev/null +++ b/app/src/main/res/layout/activity_details_review.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_download.xml b/app/src/main/res/layout/activity_download.xml new file mode 100644 index 000000000..3fda42dde --- /dev/null +++ b/app/src/main/res/layout/activity_download.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_generic_pager.xml b/app/src/main/res/layout/activity_generic_pager.xml new file mode 100644 index 000000000..bcdc3c1e6 --- /dev/null +++ b/app/src/main/res/layout/activity_generic_pager.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_generic_recycler.xml b/app/src/main/res/layout/activity_generic_recycler.xml new file mode 100644 index 000000000..50b15d762 --- /dev/null +++ b/app/src/main/res/layout/activity_generic_recycler.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_google.xml b/app/src/main/res/layout/activity_google.xml new file mode 100644 index 000000000..25126cee3 --- /dev/null +++ b/app/src/main/res/layout/activity_google.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..40f5026ac --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml new file mode 100644 index 000000000..5772baad1 --- /dev/null +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_screenshot.xml b/app/src/main/res/layout/activity_screenshot.xml new file mode 100644 index 000000000..b9a73edf6 --- /dev/null +++ b/app/src/main/res/layout/activity_screenshot.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_search_result.xml b/app/src/main/res/layout/activity_search_result.xml new file mode 100644 index 000000000..87c06a35b --- /dev/null +++ b/app/src/main/res/layout/activity_search_result.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search_suggestion.xml b/app/src/main/res/layout/activity_search_suggestion.xml new file mode 100644 index 000000000..7f56e2126 --- /dev/null +++ b/app/src/main/res/layout/activity_search_suggestion.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml new file mode 100644 index 000000000..4e8e84859 --- /dev/null +++ b/app/src/main/res/layout/activity_setting.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 000000000..3752f37e6 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_apps_games.xml b/app/src/main/res/layout/fragment_apps_games.xml new file mode 100644 index 000000000..f33859480 --- /dev/null +++ b/app/src/main/res/layout/fragment_apps_games.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_screenshots.xml b/app/src/main/res/layout/fragment_details_screenshots.xml new file mode 100644 index 000000000..465f0618f --- /dev/null +++ b/app/src/main/res/layout/fragment_details_screenshots.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_for_you.xml b/app/src/main/res/layout/fragment_for_you.xml new file mode 100644 index 000000000..25ea52ad8 --- /dev/null +++ b/app/src/main/res/layout/fragment_for_you.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_generic_recycler.xml b/app/src/main/res/layout/fragment_generic_recycler.xml new file mode 100644 index 000000000..995b0324c --- /dev/null +++ b/app/src/main/res/layout/fragment_generic_recycler.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_accent.xml b/app/src/main/res/layout/fragment_onboarding_accent.xml new file mode 100644 index 000000000..7687bd122 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_accent.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_installer.xml b/app/src/main/res/layout/fragment_onboarding_installer.xml new file mode 100644 index 000000000..2ab7c8eb4 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_installer.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_theme.xml b/app/src/main/res/layout/fragment_onboarding_theme.xml new file mode 100644 index 000000000..e9cc530cc --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_theme.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_welcome.xml b/app/src/main/res/layout/fragment_onboarding_welcome.xml new file mode 100644 index 000000000..db1cb2e25 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_welcome.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_top_chart.xml b/app/src/main/res/layout/fragment_top_chart.xml new file mode 100644 index 000000000..39f5aa492 --- /dev/null +++ b/app/src/main/res/layout/fragment_top_chart.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_top_container.xml b/app/src/main/res/layout/fragment_top_container.xml new file mode 100644 index 000000000..2a10291cb --- /dev/null +++ b/app/src/main/res/layout/fragment_top_container.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_updates.xml b/app/src/main/res/layout/fragment_updates.xml new file mode 100644 index 000000000..720464a79 --- /dev/null +++ b/app/src/main/res/layout/fragment_updates.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_preference.xml b/app/src/main/res/layout/item_preference.xml new file mode 100644 index 000000000..83267fa75 --- /dev/null +++ b/app/src/main/res/layout/item_preference.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_app.xml b/app/src/main/res/layout/layout_details_app.xml new file mode 100644 index 000000000..b502b2ee7 --- /dev/null +++ b/app/src/main/res/layout/layout_details_app.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_description.xml b/app/src/main/res/layout/layout_details_description.xml new file mode 100644 index 000000000..0e7d783ba --- /dev/null +++ b/app/src/main/res/layout/layout_details_description.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_dev.xml b/app/src/main/res/layout/layout_details_dev.xml new file mode 100644 index 000000000..11dedc1aa --- /dev/null +++ b/app/src/main/res/layout/layout_details_dev.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_info.xml b/app/src/main/res/layout/layout_details_info.xml new file mode 100644 index 000000000..52a5cbf4c --- /dev/null +++ b/app/src/main/res/layout/layout_details_info.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_install.xml b/app/src/main/res/layout/layout_details_install.xml new file mode 100644 index 000000000..19c52600d --- /dev/null +++ b/app/src/main/res/layout/layout_details_install.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_privacy.xml b/app/src/main/res/layout/layout_details_privacy.xml new file mode 100644 index 000000000..97179775a --- /dev/null +++ b/app/src/main/res/layout/layout_details_privacy.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_review.xml b/app/src/main/res/layout/layout_details_review.xml new file mode 100644 index 000000000..38913d1f0 --- /dev/null +++ b/app/src/main/res/layout/layout_details_review.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_nav_header.xml b/app/src/main/res/layout/layout_nav_header.xml new file mode 100644 index 000000000..9e1ea3ba1 --- /dev/null +++ b/app/src/main/res/layout/layout_nav_header.xml @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/model_carousel_group.xml b/app/src/main/res/layout/model_carousel_group.xml new file mode 100644 index 000000000..45dc62e1a --- /dev/null +++ b/app/src/main/res/layout/model_carousel_group.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/model_editorchoice_group.xml b/app/src/main/res/layout/model_editorchoice_group.xml new file mode 100644 index 000000000..8f258ad49 --- /dev/null +++ b/app/src/main/res/layout/model_editorchoice_group.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/multi_recycler.xml b/app/src/main/res/layout/multi_recycler.xml new file mode 100644 index 000000000..d76c87d1d --- /dev/null +++ b/app/src/main/res/layout/multi_recycler.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml new file mode 100644 index 000000000..15898ab7e --- /dev/null +++ b/app/src/main/res/layout/settings_activity.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_app_menu.xml b/app/src/main/res/layout/sheet_app_menu.xml new file mode 100644 index 000000000..516057fe3 --- /dev/null +++ b/app/src/main/res/layout/sheet_app_menu.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_app_peek.xml b/app/src/main/res/layout/sheet_app_peek.xml new file mode 100644 index 000000000..7055a8d64 --- /dev/null +++ b/app/src/main/res/layout/sheet_app_peek.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_base.xml b/app/src/main/res/layout/sheet_base.xml new file mode 100644 index 000000000..2b86208ca --- /dev/null +++ b/app/src/main/res/layout/sheet_base.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_download_menu.xml b/app/src/main/res/layout/sheet_download_menu.xml new file mode 100644 index 000000000..b16cfb016 --- /dev/null +++ b/app/src/main/res/layout/sheet_download_menu.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_network.xml b/app/src/main/res/layout/sheet_network.xml new file mode 100644 index 000000000..146ae90e0 --- /dev/null +++ b/app/src/main/res/layout/sheet_network.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_accent.xml b/app/src/main/res/layout/view_accent.xml new file mode 100644 index 000000000..40813724e --- /dev/null +++ b/app/src/main/res/layout/view_accent.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_action_button.xml b/app/src/main/res/layout/view_action_button.xml new file mode 100644 index 000000000..b4369a3c8 --- /dev/null +++ b/app/src/main/res/layout/view_action_button.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_action_header.xml b/app/src/main/res/layout/view_action_header.xml new file mode 100644 index 000000000..b5cabb350 --- /dev/null +++ b/app/src/main/res/layout/view_action_header.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app.xml b/app/src/main/res/layout/view_app.xml new file mode 100644 index 000000000..51279a73d --- /dev/null +++ b/app/src/main/res/layout/view_app.xml @@ -0,0 +1,59 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_card_shimmer.xml b/app/src/main/res/layout/view_app_card_shimmer.xml new file mode 100644 index 000000000..54af8ed3b --- /dev/null +++ b/app/src/main/res/layout/view_app_card_shimmer.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_dependent.xml b/app/src/main/res/layout/view_app_dependent.xml new file mode 100644 index 000000000..413d0069d --- /dev/null +++ b/app/src/main/res/layout/view_app_dependent.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_list.xml b/app/src/main/res/layout/view_app_list.xml new file mode 100644 index 000000000..006e656c6 --- /dev/null +++ b/app/src/main/res/layout/view_app_list.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_list_shimmer.xml b/app/src/main/res/layout/view_app_list_shimmer.xml new file mode 100644 index 000000000..8ff188064 --- /dev/null +++ b/app/src/main/res/layout/view_app_list_shimmer.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_progress.xml b/app/src/main/res/layout/view_app_progress.xml new file mode 100644 index 000000000..52f4f5281 --- /dev/null +++ b/app/src/main/res/layout/view_app_progress.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_shimmer.xml b/app/src/main/res/layout/view_app_shimmer.xml new file mode 100644 index 000000000..dcafeacbe --- /dev/null +++ b/app/src/main/res/layout/view_app_shimmer.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_update.xml b/app/src/main/res/layout/view_app_update.xml new file mode 100644 index 000000000..0bba47a7c --- /dev/null +++ b/app/src/main/res/layout/view_app_update.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_badge.xml b/app/src/main/res/layout/view_badge.xml new file mode 100644 index 000000000..a3497b78b --- /dev/null +++ b/app/src/main/res/layout/view_badge.xml @@ -0,0 +1,45 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_black.xml b/app/src/main/res/layout/view_black.xml new file mode 100644 index 000000000..0c4c46734 --- /dev/null +++ b/app/src/main/res/layout/view_black.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_category.xml b/app/src/main/res/layout/view_category.xml new file mode 100644 index 000000000..9a6295f27 --- /dev/null +++ b/app/src/main/res/layout/view_category.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_dash.xml b/app/src/main/res/layout/view_dash.xml new file mode 100644 index 000000000..0d008c09a --- /dev/null +++ b/app/src/main/res/layout/view_dash.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_dev_info.xml b/app/src/main/res/layout/view_dev_info.xml new file mode 100644 index 000000000..88e0401b1 --- /dev/null +++ b/app/src/main/res/layout/view_dev_info.xml @@ -0,0 +1,60 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_device.xml b/app/src/main/res/layout/view_device.xml new file mode 100644 index 000000000..d709498ce --- /dev/null +++ b/app/src/main/res/layout/view_device.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_download.xml b/app/src/main/res/layout/view_download.xml new file mode 100644 index 000000000..0264b6fa6 --- /dev/null +++ b/app/src/main/res/layout/view_download.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_editor_head.xml b/app/src/main/res/layout/view_editor_head.xml new file mode 100644 index 000000000..442c31f3c --- /dev/null +++ b/app/src/main/res/layout/view_editor_head.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_editor_image.xml b/app/src/main/res/layout/view_editor_image.xml new file mode 100644 index 000000000..ba9d86d2c --- /dev/null +++ b/app/src/main/res/layout/view_editor_image.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_exodus.xml b/app/src/main/res/layout/view_exodus.xml new file mode 100644 index 000000000..8e8a7c795 --- /dev/null +++ b/app/src/main/res/layout/view_exodus.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_file.xml b/app/src/main/res/layout/view_file.xml new file mode 100644 index 000000000..a5cb2a2a5 --- /dev/null +++ b/app/src/main/res/layout/view_file.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header.xml b/app/src/main/res/layout/view_header.xml new file mode 100644 index 000000000..437a5e720 --- /dev/null +++ b/app/src/main/res/layout/view_header.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header_shimmer.xml b/app/src/main/res/layout/view_header_shimmer.xml new file mode 100644 index 000000000..98a039d49 --- /dev/null +++ b/app/src/main/res/layout/view_header_shimmer.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header_update.xml b/app/src/main/res/layout/view_header_update.xml new file mode 100644 index 000000000..f94beb8bc --- /dev/null +++ b/app/src/main/res/layout/view_header_update.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_installer.xml b/app/src/main/res/layout/view_installer.xml new file mode 100644 index 000000000..53ad1e8f0 --- /dev/null +++ b/app/src/main/res/layout/view_installer.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link.xml b/app/src/main/res/layout/view_link.xml new file mode 100644 index 000000000..dca873f40 --- /dev/null +++ b/app/src/main/res/layout/view_link.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_locale.xml b/app/src/main/res/layout/view_locale.xml new file mode 100644 index 000000000..4f30ea990 --- /dev/null +++ b/app/src/main/res/layout/view_locale.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_more_badge.xml b/app/src/main/res/layout/view_more_badge.xml new file mode 100644 index 000000000..5fb17d4da --- /dev/null +++ b/app/src/main/res/layout/view_more_badge.xml @@ -0,0 +1,57 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_no_app.xml b/app/src/main/res/layout/view_no_app.xml new file mode 100644 index 000000000..7cc47a1ed --- /dev/null +++ b/app/src/main/res/layout/view_no_app.xml @@ -0,0 +1,42 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_no_app_alt.xml b/app/src/main/res/layout/view_no_app_alt.xml new file mode 100644 index 000000000..d0d347892 --- /dev/null +++ b/app/src/main/res/layout/view_no_app_alt.xml @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_rating.xml b/app/src/main/res/layout/view_rating.xml new file mode 100644 index 000000000..68071cc5c --- /dev/null +++ b/app/src/main/res/layout/view_rating.xml @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/app/src/main/res/layout/view_review.xml b/app/src/main/res/layout/view_review.xml new file mode 100644 index 000000000..8d4a4dd5c --- /dev/null +++ b/app/src/main/res/layout/view_review.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_screenshot.xml b/app/src/main/res/layout/view_screenshot.xml new file mode 100644 index 000000000..5a6f384f8 --- /dev/null +++ b/app/src/main/res/layout/view_screenshot.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_screenshot_large.xml b/app/src/main/res/layout/view_screenshot_large.xml new file mode 100644 index 000000000..0012b2816 --- /dev/null +++ b/app/src/main/res/layout/view_screenshot_large.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_search_suggestion.xml b/app/src/main/res/layout/view_search_suggestion.xml new file mode 100644 index 000000000..4a3cf2b26 --- /dev/null +++ b/app/src/main/res/layout/view_search_suggestion.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_state_button.xml b/app/src/main/res/layout/view_state_button.xml new file mode 100644 index 000000000..225249260 --- /dev/null +++ b/app/src/main/res/layout/view_state_button.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_theme.xml b/app/src/main/res/layout/view_theme.xml new file mode 100644 index 000000000..84baf03a6 --- /dev/null +++ b/app/src/main/res/layout/view_theme.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_action.xml b/app/src/main/res/layout/view_toolbar_action.xml new file mode 100644 index 000000000..d02942fd0 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_action.xml @@ -0,0 +1,51 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_main.xml b/app/src/main/res/layout/view_toolbar_main.xml new file mode 100644 index 000000000..8bf17ed61 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_main.xml @@ -0,0 +1,64 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_native.xml b/app/src/main/res/layout/view_toolbar_native.xml new file mode 100644 index 000000000..6ae162a55 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_native.xml @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_search.xml b/app/src/main/res/layout/view_toolbar_search.xml new file mode 100644 index 000000000..a6b7a0df2 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_search.xml @@ -0,0 +1,63 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_two_column.xml b/app/src/main/res/layout/view_two_column.xml new file mode 100644 index 000000000..83eea5d55 --- /dev/null +++ b/app/src/main/res/layout/view_two_column.xml @@ -0,0 +1,49 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_app.xml b/app/src/main/res/menu/menu_app.xml new file mode 100644 index 000000000..eb64de0b8 --- /dev/null +++ b/app/src/main/res/menu/menu_app.xml @@ -0,0 +1,37 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_bottom_nav.xml b/app/src/main/res/menu/menu_bottom_nav.xml new file mode 100644 index 000000000..796c0bc45 --- /dev/null +++ b/app/src/main/res/menu/menu_bottom_nav.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_details.xml b/app/src/main/res/menu/menu_details.xml new file mode 100644 index 000000000..ed4b61de8 --- /dev/null +++ b/app/src/main/res/menu/menu_details.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_download_main.xml b/app/src/main/res/menu/menu_download_main.xml new file mode 100644 index 000000000..00682a59d --- /dev/null +++ b/app/src/main/res/menu/menu_download_main.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_download_single.xml b/app/src/main/res/menu/menu_download_single.xml new file mode 100644 index 000000000..87b4484b4 --- /dev/null +++ b/app/src/main/res/menu/menu_download_single.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_drawer.xml b/app/src/main/res/menu/menu_drawer.xml new file mode 100644 index 000000000..864c17dd4 --- /dev/null +++ b/app/src/main/res/menu/menu_drawer.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_splash.xml b/app/src/main/res/menu/menu_splash.xml new file mode 100644 index 000000000..76a7583d6 --- /dev/null +++ b/app/src/main/res/menu/menu_splash.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..8eb609edd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..8eb609edd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..685298993 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..685298993 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5769c31fc Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..5769c31fc Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..fff4e420e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..fff4e420e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..65ace096d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..65ace096d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..6b2ab3c26 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..6b2ab3c26 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml new file mode 100644 index 000000000..ea01bdbc6 --- /dev/null +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation_onboarding.xml b/app/src/main/res/navigation/navigation_onboarding.xml new file mode 100644 index 000000000..7a2eb9691 --- /dev/null +++ b/app/src/main/res/navigation/navigation_onboarding.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles_text.xml b/app/src/main/res/values-v21/styles_text.xml new file mode 100644 index 000000000..bf52210e0 --- /dev/null +++ b/app/src/main/res/values-v21/styles_text.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v26/styles_text.xml b/app/src/main/res/values-v26/styles_text.xml new file mode 100644 index 000000000..91662e959 --- /dev/null +++ b/app/src/main/res/values-v26/styles_text.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 000000000..4342215f2 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,160 @@ + + + + + 12sp + 13sp + 14sp + 15sp + 16sp + 17sp + 18sp + 19sp + 20sp + + + 24sp + 25sp + 26sp + 27sp + 28sp + 29sp + 30sp + 31sp + 32sp + 33sp + 34sp + 35sp + 36sp + 37sp + 38sp + 39sp + 40sp + 41sp + 42sp + 43sp + 44sp + 45sp + 46sp + 47sp + 48sp + + + + @string/pref_ui_theme_system + @string/pref_ui_theme_light + @string/pref_ui_theme_dark + @string/pref_ui_theme_black + @string/pref_ui_theme_dark_x + @string/pref_ui_theme_darkord + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + @string/ui_accent_1 + @string/ui_accent_2 + @string/ui_accent_3 + @string/ui_accent_4 + @string/ui_accent_5 + @string/ui_accent_6 + @string/ui_accent_7 + @string/ui_accent_8 + @string/ui_accent_9 + @string/ui_accent_10 + @string/ui_accent_11 + @string/ui_accent_12 + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + + @string/pref_install_mode_session + @string/pref_install_mode_native + @string/pref_install_mode_root + @string/pref_install_mode_services + + + + 0 + 1 + 2 + 3 + + + + "bc1qu7cy9fepjj309y4r2x3rymve7mw4ff39c8cpe0" + "qpqus3qdlz8guf476vwz0fjl8s34fseukcmrl6eknl" + "0x6977446933EC8b5964D921f7377950992337B1C6" + "whyorean@dbs" + "https://paypal.me/AuroraDev" + "https://liberapay.com/on/gitlab/whyorean/ " + "https://gitlab.com/AuroraOSS/AuroraStore" + "https://forum.xda-developers.com/android/apps-games/galaxy-playstore-alternative-t3739733" + "https://t.me/AuroraSupport" + "https://f-droid.org/en/packages/com.aurora.store/" + + + + @string/about_bitcoin_btc + @string/about_bitcoin_bch + @string/about_bitcoin_eth + @string/about_bhim + @string/about_paypal + @string/about_libera + @string/about_gitlab + @string/about_xda + @string/about_telegram + @string/about_fdroid + + + + @string/about_bitcoin_btc_summary + @string/about_bitcoin_bch_summary + @string/about_bitcoin_eth_summary + @string/about_bhim_summary + @string/about_paypal_summary + @string/about_libera_summary + @string/about_gitlab_summary + @string/about_xda_summary + @string/about_telegram_summary + @string/about_fdroid_summary + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 000000000..1fc263af5 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..08c47e678 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,88 @@ + + + + + @color/colorAccent + #9D9D9D + #ABABAB + #D6D6D6 + + + #202124 + #2d2e31 + #202124 + #373a40 + #373a40 + + + #1C2733 + #222E3C + #1C2733 + #425a75 + #425a75 + + + #23272a + #2c2f33 + #23272a + #99aab5 + #99aab5 + + + #000 + #646464 + #343434 + + + #6C63FF + #FFFFFF + #00000000 + + + @color/colorAccent + @color/colorScrim + #44F2F2F2 + #F2F2F2 + + + #22000000 + #99000000 + #446C63FF + #30000000 + + #4768FD + #E4002B + #ff4c4c + #FF9933 + #49A942 + + #6C63FF + #FF00FF + #E4002B + #F9A826 + #49A942 + #6633cc + #52565e + #ee70a6 + #b5c327 + #f38654 + #61A7E0 + #7289da + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..1d2d42161 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,62 @@ + + + + 2dp + 4dp + 8dp + 10dp + 12dp + 16dp + 32dp + + 2dp + 4dp + 8dp + 10dp + 12dp + 16dp + 32dp + + 24dp + 36dp + 48dp + 56dp + 64dp + 72dp + 80dp + 108dp + + + 240dp + 135dp + + 64dp + + 128dp + 3dp + + 36dp + 52dp + 56dp + 200dp + 52dp + 64dp + 148dp + \ No newline at end of file diff --git a/app/src/main/res/values/font_certs.xml b/app/src/main/res/values/font_certs.xml new file mode 100644 index 000000000..4a9cfec0b --- /dev/null +++ b/app/src/main/res/values/font_certs.xml @@ -0,0 +1,35 @@ + + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs= + + + + + MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK + + + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..461765417 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,22 @@ + + + + #6C63FF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..d5bf5dd94 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,291 @@ + + + + "BHIM - UPI" + "Donate via UPI" + "Bitcoin Cash" + "Donate via Bitcoin Cash (BCH)" + "Bitcoin" + "Donate via Bitcoin (BTC)" + "Ethereum" + "Donate via Ethereum (ETH)" + "F-Droid" + "Get the Aurora Store via F-Droid." + "GitLab" + "Get the Aurora Store source code from Aurora OSS on GitLab." + "Liberapay" + "Become a patron on Liberapay" + "Paypal" + "Donate via PayPal" + "Telegram" + "Join the Aurora Support group for discussions or suggestions." + "Dev Thread" + "See the Aurora Store XDA thread for discussions or suggestions." + + "Anonymous" + "Google" + "You are logged out" + "Login using" + "Logout from Aurora" + + "Accounts" + "All reviews" + "Ask" + "Back" + "Blacklist" + "Add to Blacklist" + "Buy" + "Cancel" + "Clear" + "Clear all" + "Clear data" + "Copy Link" + "Disable" + "Discard" + "Discover" + "Done" + "Download" + "Enable" + "Export" + "Favourite apps" + "Filters" + "All" + "Apply" + "Apps with ads" + "Downloads" + "GSF dependent" + "Misc" + "Paid" + "Ratings" + "Finish" + "Save App bundle" + "Home" + "Ignore" + Ignore version + "Import" + "Install" + "Installations" + "Installed" + "Installing" + "Later" + "Logging in" + "Logout" + "Manual download" + "More" + "Next" + "Open" + "Pause" + "Post" + "Previous" + "Purchase" + "Resend" + "Restart" + "Resume" + "Retry" + "Save" + "Search" + "Settings" + "Share" + "Sign in" + "Sign up" + "Skip" + "Spoof" + "Submit" + "Uninstall" + "App Info" + "Update" + "Update all" + "Updates" + "Wait" + "Whitelist" + "Add to wishlist" + + "Changelog" + "Changelog not provided" + "Contains Ads" + "Dependencies" + "Description" + "Address" + "Developer contact" + "Email" + "Developer\'s reply" + "Website" + "Files" + "Free" + "Requires GSF" + "In-app purchase" + "More about app" + "No ads" + "No apps available" + "No Dependencies" + "No previews available" + "No reviews available" + "No updates available" + "No apps whitelisted" + "See what others think about this app" + "Paid" + "Privacy" + "Enter star rating first" + "Rate this app" + "Rating and reviews" + "Review submitted" + "Reviews" + "More reviews" + "Screenshots" + "What do you think about this app?" + + "Cancel all" + "Cancelled" + "Clear completed" + "Downloaded" + "Deleted" + "Calculating" + "Download failed" + "Force clear all" + "Getting metadata" + "No downloads" + "Pause" + "Pause all" + "Paused" + "Downloading" + "Queued" + "Resume" + "Resume all" + "Estimating" + + "No reports available" + "Contains no trackers" + "Powered by Exodus" + "Checking for trackers…" + "Exodus report" + "trackers(s) found in" + "Aurora Protect" + "Exodus report" + "View" + "View report" + + "All" + "Critical" + "Five" + "Four" + "One" + "Positive" + "Three" + "Two" + + "Root access available" + "No Root, grant root access or change the installer." + "Installation failed" + "Installation cancelled" + "Installation was blocked" + "Conflicting package exists" + "Incompatible app" + "Invalid or corrupted APK" + "Insufficient memory space" + "Successfully installed" + "Unknown" + "Waiting for user confirmation" + + "Disclaimer" + "License" + "Terms of service" + + "Quick notification" + "General notification" + "Click to install" + + "Select a suitable installer" + "Select what you like" + "Accent" + "Installer" + "Themes" + "Welcome" + "How you doing?" + + "Force clear installation sessions" + "Clear abandon or pending installation sessions" + "Downloads" + "This setting will be applied after you restart the app" + "Maximum number of active downloads" + "Active download" + "Removes F-Droid apps from app lists" + "Filter F-Droid apps" + "Removes all Google Apps from search results and category apps" + "Filter Google apps" + "Apps are installed instantly after download completes" + "Auto install APKs after download" + "APKs will be deleted by default" + "Delete APK post-install" + "Native installer" + "Root installer" + "Aurora Services" + "Session installer" + "Select mode of APK installation" + "Installation method" + "Select the target profile to install apps to. Only works with the root installation method" + "Installation profiles (Experimental)" + "Accent" + "Black" + "Dark" + "Dark X" + "Disskord" + "Light" + "Follow System" + "Theme" + "Customization" + + "Download Failed" + "App not purchased" + "Could not get files" + + "Make sure you re-login to apply the spoof" + + "Top free" + "Top grossing" + "Top paid" + "Trending" + + "About" + "Accounts" + "Apps" + "My apps & games" + "Apps in library" + "Apps on sale" + "Blacklist manager" + "Details" + "Device" + "Downloads" + "Games" + "Installation" + "Installed" + "Language" + "Library" + "Settings" + "Spoof manager" + "Updates" + + "Old installation sessions abandoned" + "Not available on anonymous accounts" + "Blacklisted" + "APK export failed" + "APK exported successfully" + "Whitelisted" + "Not available on anonymous accounts" + \ No newline at end of file diff --git a/app/src/main/res/values/strings_do_not_translate.xml b/app/src/main/res/values/strings_do_not_translate.xml new file mode 100644 index 000000000..e24a27e0b --- /dev/null +++ b/app/src/main/res/values/strings_do_not_translate.xml @@ -0,0 +1,43 @@ + + + + "Aurora Store" + + "%s • %s★ • %s" + %1$dh %2$dm %3$ds left + %1$dm %2$ds left + %1$ds left + %1$d B/s + %1$s KB/s + %1$s MB/s + + Blue + Fuchsia + Red + Golden + Green + Purple + Grey + Pink + Lime + Orange + Light blue + Disskord blue + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..3bd5252ec --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_accent.xml b/app/src/main/res/values/styles_accent.xml new file mode 100644 index 000000000..24dba4461 --- /dev/null +++ b/app/src/main/res/values/styles_accent.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_text.xml b/app/src/main/res/values/styles_text.xml new file mode 100644 index 000000000..209ca0b62 --- /dev/null +++ b/app/src/main/res/values/styles_text.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_widget.xml b/app/src/main/res/values/styles_widget.xml new file mode 100644 index 000000000..6067c1ba8 --- /dev/null +++ b/app/src/main/res/values/styles_widget.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/paths.xml b/app/src/main/res/xml/paths.xml new file mode 100644 index 000000000..20f667683 --- /dev/null +++ b/app/src/main/res/xml/paths.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_download.xml b/app/src/main/res/xml/preferences_download.xml new file mode 100644 index 000000000..1225f2e87 --- /dev/null +++ b/app/src/main/res/xml/preferences_download.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/xml/preferences_filter.xml b/app/src/main/res/xml/preferences_filter.xml new file mode 100644 index 000000000..a399a89f5 --- /dev/null +++ b/app/src/main/res/xml/preferences_filter.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_installation.xml b/app/src/main/res/xml/preferences_installation.xml new file mode 100644 index 000000000..49fd413d4 --- /dev/null +++ b/app/src/main/res/xml/preferences_installation.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml new file mode 100644 index 000000000..95701e10d --- /dev/null +++ b/app/src/main/res/xml/preferences_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_ui.xml b/app/src/main/res/xml/preferences_ui.xml new file mode 100644 index 000000000..bb37d8fdf --- /dev/null +++ b/app/src/main/res/xml/preferences_ui.xml @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/app/src/test/java/com/aurora/store/ExampleUnitTest.kt b/app/src/test/java/com/aurora/store/ExampleUnitTest.kt new file mode 100644 index 000000000..b63b40f22 --- /dev/null +++ b/app/src/test/java/com/aurora/store/ExampleUnitTest.kt @@ -0,0 +1,35 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store + +import org.junit.Assert.* +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..9365ca05c --- /dev/null +++ b/build.gradle @@ -0,0 +1,50 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +buildscript { + + repositories { + google() + jcenter() + } + ext { + kotlin_version = '1.4.30' + } + + ext.kotlin = "1.4.30" + ext.navigation= "2.3.3" + + dependencies { + classpath ("com.android.tools.build:gradle:4.1.2") + classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin") + classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:$navigation") + } +} + +allprojects { + repositories { + google() + jcenter() + maven { url 'https://jitpack.io' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..0263fde23 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,57 @@ +# +# Aurora Store +# Copyright (C) 2021, Rahul Kumar Patel +# +# 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 . +# +# + +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# Enable Kapt Incremental annotation processing requeste +kapt.incremental.apt=true + +# Enable android.databinding.annotationprocessor.ProcessDataBinding (DYNAMIC) +android.databinding.incremental=true +android.lifecycleProcessor.incremental=true + +# Decrease gradle builds time +kapt.use.worker.api=true + +# turn off AP discovery in compile path, and therefore turn on Compile Avoidance +kapt.include.compile.classpath=false + +# Enable In Logcat to determine Kapt +kapt.verbose=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..f6b961fd5 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..6a0f54918 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,25 @@ +# +# Aurora Store +# Copyright (C) 2021, Rahul Kumar Patel +# +# 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 . +# +# + +#Fri Oct 16 03:09:47 IST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..e1566ee31 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,21 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +include ':app' +rootProject.name = "AuroraStore4" \ No newline at end of file