Merge branch 'v4'
This commit is contained in:
55
DISCLAIMER.md
Normal file
55
DISCLAIMER.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Disclaimer
|
||||
|
||||
### 1. Google Accounts.
|
||||
|
||||
You hold yourself accountable if you are to use Aurora Store with your personal Google Account, which may cause your account to be blocked/blacklisted by Google.
|
||||
|
||||
As per Google Play's Terms Of Service §4 and §2:
|
||||
|
||||
#### §4 Rights and Restrictions
|
||||
|
||||
> **Violation of License Terms.** If you violate any of the Content restriction provisions of the Terms, materially or repeatedly violate any of the other Terms or we are investigating you for suspected misconduct then Google may, without refund to you, terminate your access to Google Play, the Content or your Google Account and your rights under this licence will immediately terminate. We will notify you with the reason for our action unless we reasonably believe that to do so would cause Google or another person legal liability, would compromise an investigation, would compromise the operation of any Google products, services or systems, would cause harm to our users or would be in breach of the law or the direction of a legal enforcement authority. See Disabled Accounts in Section 2 for additional information.
|
||||
>
|
||||
> **Restrictions:** You may not:
|
||||
>
|
||||
> - display (in part or in whole) the Content as part of any public performance or display even if no fee is charged except (a) where such use would not constitute a copyright infringement or violate any other applicable right or (b) as specifically permitted and only in the exact manner provided.
|
||||
> - sell, rent, lease, redistribute, broadcast, transmit, communicate, modify, sublicense, transfer, assign any Content to any third party including with regard to any downloads of Content that you may obtain through Google Play except as specifically permitted and only in the exact manner provided.
|
||||
> - use Google Play or any Content in conjunction with any stream-ripping, stream capture or similar software to record or create a copy of any Content that is presented to you in streaming format.
|
||||
> - use Content as part of any service for sharing, lending or multi-person use, or for the purpose of any other institution, except as specifically permitted and only in the exact manner provided.
|
||||
> - attempt to, or assist, authorize or encourage others to circumvent, disable or defeat any of the security features or components that protect, obfuscate or otherwise restrict access to any Content or Google Play.
|
||||
> - remove any watermarks, labels or other legal or proprietary notices included in any Content, or attempt to modify any Content obtained through Google Play, including any modification for the purpose of disguising or changing any indications of the ownership or source of Content.
|
||||
>
|
||||
> **Multiple Accounts.** If you have multiple Google Accounts with different user names, in some cases you may transfer Content out of an account and into another account, provided you are the owner of each such account and provided Google has enabled a feature of the relevant service allowing such transfers.
|
||||
|
||||
#### §2 Your Use of Google Play
|
||||
|
||||
> **Disabled Accounts.** If Google disables access to your account in accordance with the Terms (for example if you materially or repeatedly violate the Terms), you may be prevented from accessing Google Play, your account details or any files or other Content that is stored with your account. See the Help Center for more information. If you are the family manager of a family on Google Play and Google disables access to your account, your family members may lose access to family features requiring a family group, such as a family payment method, family subscriptions, or Content shared by family members. If you are a family member of a family on Google Play and Google disables your account, your family members will lose access to Content you have shared with them.
|
||||
|
||||
### 2. Installing **Aurora Store** from third-party sources.
|
||||
|
||||
You are to hold yourself responsible by installing **Aurora Store** from other sources other than our own, in which you do not blame us for malicious programs, device bricking or anything else.
|
||||
|
||||
The following sources are the only official links we provide:
|
||||
|
||||
- **F-Droid:** https://f-droid.org/en/packages/com.aurora.store/
|
||||
- **AuroraOSS Website:** https://auroraoss.com/downloads.php
|
||||
- **Official Telegram Channel:** https://t.me/AuroraOfficial
|
||||
- **GitLab:** https://gitlab.com/AuroraOSS/AuroraStore/-/releases
|
||||
|
||||
### 3. Links in Aurora Store
|
||||
|
||||
**AuroraOSS**, makes no claim or representation regarding, and accepts no responsibility for third party websites accessible by hyperlink from the Service or websites linking to the Service. When you leave the Service, you should be aware that these Terms and our policies no longer govern.
|
||||
|
||||
The only links we provide are:
|
||||
|
||||
- **Donation links** Bitcoin, Bitcoin Cash, Etherum, BHIM-UPI, PayPal & Liberapay links
|
||||
- **Repository sources** GitLab repository link
|
||||
- **xda-developers** Aurora Store xda-developers thread
|
||||
- **Telegram** Aurora Support group link
|
||||
- **F-Droid** Aurora Store in F-Droid repository link
|
||||
|
||||
### 4. Content
|
||||
|
||||
We do not own any content within **Aurora Store**, all available content i.e. applications, information and links are provided "as is" directly from Google Play repositories/servers.
|
||||
|
||||
For all content on **Aurora Store**, we don't review, verify or authenticate it, and it may include inaccuracies or false information. We make no representations, warranties, or guarantees relating to the quality, suitability, truth, accuracy or completeness of any content contained in the Service. You acknowledge sole responsibility for and assume all risk arising from your use of or reliance on any content.
|
||||
35
POLICY.md
Normal file
35
POLICY.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Aurora Store Privacy Policy
|
||||
|
||||
Last Updated: **17.02.2021**
|
||||
|
||||
## User data sent to Google by default
|
||||
|
||||
The following data is mandatory to make the Service function, all of which Google needs for **Aurora Store** to be able to receive required data:
|
||||
|
||||
- **IP Address** which is bound to network request
|
||||
- **Timezone, MCC & MNC** are stripped & replaced with a random constant
|
||||
- **BUILD_CONFIG** includes the app details - package name,
|
||||
version-name, version-code
|
||||
- **BUILD** includes your device details, see for more info
|
||||
|
||||
If you choose to use your own account, the following are added to the above:
|
||||
|
||||
- **Account details** for Google sign-in verification
|
||||
|
||||
We do not store, process or sell any of the collected data above. The data is sent to and processed by Google's servers directly untouched.
|
||||
|
||||
All accounts being used within **Aurora Store** are saved onto the device as AAS Tokens & email pair for account verification. **No passwords** are saved to Aurora Store.
|
||||
|
||||
## User data sent to our server by default
|
||||
|
||||
The only data being sent to our server are **IP addresses**. This is to ensure the functionality and stability of our server, which detects the amount of GET requests from an IP address and if deemed them as spamming, will be banned from our server, otherwise rate-limited.
|
||||
|
||||
Rate-limiting lasts for an hour. If you keep going over the 20 request per hour limit, an additional hour to the rate-limiting will be added for every addtional 20 requests. So do yourself a favour and don't do it.
|
||||
|
||||
## Changes To This Privacy Policy
|
||||
|
||||
We may update our Privacy Policy from time to time. You are thus advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
|
||||
|
||||
## Contact Us
|
||||
|
||||
If you have any questions about this Privacy Policy, please contact us at **aurora.dev@gmail.com** or via our support groups on Telegram.
|
||||
83
README.md
Normal file
83
README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
<div align="right">
|
||||
<img src="https://www.gnu.org/graphics/gplv3-88x31.png" alt="GPL v3 Logo" />
|
||||
</div>
|
||||
<div align="center">
|
||||
<img src="app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" title="AuroraStoreLogo" alt="AuroraStoreLogo" />
|
||||
</div>
|
||||
|
||||
# Aurora Store: A Google Playstore Client
|
||||
|
||||
**Aurora Store** is an unofficial, FOSS client to Google's Play Store with an elegant design. Not only does Aurora Store download, update, and search for apps like the Play Store, it also empowers the user with new features.
|
||||
|
||||
For those concerned with privacy, **Aurora Store** does not require Google's proprietary framework (spyware?) to operate. It works perfectly fine with or without Google Play Services or [MicroG](https://microg.org/).
|
||||
|
||||
**Aurora Store** was originally based on Sergei Yeriomin's [Yalp store](https://github.com/yeriomin/YalpStore). **Aurora Store** v4.0 is a rewrite of version 3 in Kotlin that follows Material Design and runs on all devices running Android 4.4+. Read the roadmap page on our Wiki for more info!
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img src="screenshots/account.png" height="400">
|
||||
<img src="screenshots/home.png" height="400">
|
||||
<img src="screenshots/sidebar.png" height="400">
|
||||
<img src="screenshots/spoof.png" height="400">
|
||||
|
||||
## Features
|
||||
|
||||
- Free/Libre software — Has GPLv3 licence
|
||||
- Beautiful design — Built upon latest Material Design guidelines
|
||||
- Anonymous accounts — You can log in and download with anonymous accounts so you don't have to use your own account
|
||||
- Personal account login — You can download purchased apps or access your wishlist by using your own Google account
|
||||
- [Exodus](https://exodus-privacy.eu.org/) integration — Instantly see trackers an app is hiding in its code
|
||||
|
||||
## Downloads
|
||||
|
||||
Please only download the latest stable releases from GitLab or one of these sources:
|
||||
|
||||
- GitLab: [Releases](https://gitlab.com/AuroraOSS/AuroraStore/-/releases)
|
||||
- AuroraOSS: [Downloads](https://auroraoss.com/downloads.php)
|
||||
- AuroraOSS: [Stable](https://auroraoss.com/AuroraStore/Stable/)
|
||||
- F-Droid: [Link](https://f-droid.org/en/packages/com.aurora.store/)
|
||||
- Telegram: [Channel](https://t.me/AuroraOfficial)
|
||||
- XDA Forum: [Thread](https://forum.xda-developers.com/android/apps-games/galaxy-playstore-alternative-t3739733)
|
||||
|
||||
Nightlies are released here:
|
||||
|
||||
- AuroraOSS: [Nightlies](https://auroraoss.com/AuroraStore/Nightly/)
|
||||
|
||||
`Aurora Store v3 is still in development! Bugs are to be expected! Any bug reports are appreciated.`
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/packages/com.dragons.aurora/)
|
||||
|
||||
## FAQ
|
||||
|
||||
Please visit the [GitLab Wiki](https://gitlab.com/AuroraOSS/AuroraStore/-/wikis/Home). You can also reach out to us on our support chat on [Telegram](https://t.me/AuroraSupport).
|
||||
|
||||
## Translations
|
||||
|
||||
Don't see your preffered language? Click on the widget below to help translate Aurora Store!
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/aurora-store/">
|
||||
<img src="https://hosted.weblate.org/widgets/aurora-store/-/strings/horizontal-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## Project references
|
||||
|
||||
<details><summary>Open Source libraries Aurora Store uses</summary>
|
||||
|
||||
- [RX-Java](https://github.com/ReactiveX/RxJava)
|
||||
- [ButterKnife](https://github.com/JakeWharton/butterknife)
|
||||
- [OkHttp3](https://square.github.io/okhttp/)
|
||||
- [Glide](https://github.com/bumptech/glide)
|
||||
- [Fetch2](https://github.com/tonyofrancis/Fetch)
|
||||
- [GPlayApi](https://gitlab.com/AuroraOSS/gplayapi)
|
||||
- [PlayStoreApi-v2](https://github.com/whyorean/playstore-api-v2) (Deprecated! Used up till v3)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Aurora Store is based on these projects</summary>
|
||||
|
||||
- [YalpStore](https://github.com/yeriomin/YalpStore)
|
||||
- [AppCrawler](https://github.com/Akdeniz/google-play-crawler)
|
||||
- [Raccoon](https://github.com/onyxbits/raccoon4)
|
||||
- [SAI](https://github.com/Aefyr/SAI)
|
||||
|
||||
</details>
|
||||
69
TermsOfService.md
Normal file
69
TermsOfService.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# **Aurora Store** TERMS OF SERVICE
|
||||
|
||||
Last Updated: **17.02.2021**
|
||||
|
||||
These terms of service ("Terms") apply to your access and use of **Aurora Store**. Please read them carefully.
|
||||
|
||||
## Accepting these Terms
|
||||
|
||||
If you access or use the Service, it means you agree to be bound by all of the terms below. So, before you use the Service, please read all of the terms. If you don't agree to all of the terms below, please do not use the Service. Also, if a term does not make sense to you, please let us know by e-mailing **auroraoss.dev@gmail.com**.
|
||||
|
||||
## Changes to these Terms
|
||||
|
||||
We reserve the right to modify these Terms at any time. For instance, we may need to change these Terms if we come out with a new feature or for some other reason.
|
||||
|
||||
Whenever we make changes to these Terms, the changes are effective **--.02.2021** after we post such revised Terms (indicated by revising the date at the top of these Terms) or upon your acceptance if we provide a mechanism for your immediate acceptance of the revised Terms (such as a click-through confirmation or acceptance button). It is your responsibility to check **Aurora Store** for changes to these Terms.
|
||||
|
||||
If you continue to use the Service after the revised Terms go into effect, then you have accepted the changes to these Terms.
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
We have a privacy policy covering the collection, use, and disclosure of user information, which can be found at **https://gitlab.com/AuroraOSS/AuroraStore/POLICY.md**.
|
||||
|
||||
## Copyright Policy
|
||||
|
||||
We take intellectual property rights seriously, and by using a copyright policy in accordance with the GNU GENERAL PUBLIC LICENSE Version 3, we have adopted a policy of terminating, in appropriate circumstances and, at our sole discretion, access to the service for users who are deemed to be repeat infringers. Persons who wish to fork the Aurora Store project or copy code from the repository MUST comply with the used license. The license in use can be found here: **https://www.gnu.org/licenses/gpl-3.0.en.html**
|
||||
|
||||
## Disclaimer
|
||||
|
||||
We have a disclaimer covering the use of our Service and the content within **Aurora Store**, which can be found at **https://gitlab.com/AuroraOSS/AuroraStore/DISCLAIMER.md**
|
||||
|
||||
## Third-Party Services
|
||||
|
||||
We provide users with two major third party services. Your use of the Service may also include the use of applications that are developed or owned by a third party. Your use of such third party applications, websites, and services is governed by that party's own terms of service or privacy policies. We encourage you to read the terms and conditions and privacy policy of any third party application, website or service that you visit or use.
|
||||
|
||||
## Token Dispenser Server
|
||||
|
||||
We provide users with the ability to use anonymous accounts within **Aurora Store**, provided from our server as tokens. Your use of the Service agrees to not affect our server in any way deemed harmful or with malicious intent.
|
||||
|
||||
We may occasionally ask contributors to provide us with accounts. You hereby acknowledge that the accounts you make for **Aurora Store** to be given entire access to **AuroraOSS** and to be stored on our server. This does not apply to non-anonymous accounts.
|
||||
|
||||
## **Aurora Store** Materials
|
||||
|
||||
We put a lot of effort into creating the Service including, the logo and all designs, text, graphics, pictures, information and other content (excluding content within **Aurora Store**). This property is owned by us or our licensors and it is protected by open-source licenses. We grant you the right to use it.
|
||||
|
||||
## Unavoidable Legal Stuff
|
||||
|
||||
THE SERVICE AND ANY OTHER SERVICE AND CONTENT INCLUDED ON OR OTHERWISE MADE AVAILABLE TO YOU THROUGH THE SERVICE ARE PROVIDED TO YOU ON AN AS IS OR AS AVAILABLE BASIS WITHOUT ANY REPRESENTATIONS OR WARRANTIES OF ANY KIND. WE DISCLAIM ANY AND ALL WARRANTIES AND REPRESENTATIONS (EXPRESS OR IMPLIED, ORAL OR WRITTEN) WITH RESPECT TO THE SERVICE AND CONTENT INCLUDED ON OR OTHERWISE MADE AVAILABLE TO YOU THROUGH THE SERVICE WHETHER ALLEGED TO ARISE BY OPERATION OF LAW, BY REASON OF CUSTOM OR USAGE IN THE TRADE, BY COURSE OF DEALING OR OTHERWISE.
|
||||
|
||||
IN NO EVENT WILL **AURORA OSS** BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY SPECIAL, INDIRECT, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES OF ANY KIND ARISING OUT OF OR IN CONNECTION WITH THE SERVICE OR ANY OTHER SERVICE AND/OR CONTENT INCLUDED ON OR OTHERWISE MADE AVAILABLE TO YOU THROUGH THE SERVICE, REGARDLESS OF THE FORM OF ACTION, WHETHER IN CONTRACT, TORT, STRICT LIABILITY OR OTHERWISE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR ARE AWARE OF THE POSSIBILITY OF SUCH DAMAGES. OUR TOTAL LIABILITY FOR ALL CAUSES OF ACTION AND UNDER ALL THEORIES OF LIABILITY WILL BE LIMITED TO THE AMOUNT YOU PAID TO **AURORA OSS**. THIS SECTION WILL BE GIVEN FULL EFFECT EVEN IF ANY REMEDY SPECIFIED IN THIS AGREEMENT IS DEEMED TO HAVE FAILED OF ITS ESSENTIAL PURPOSE.
|
||||
|
||||
You agree to defend, indemnify and hold us harmless from and against any and all costs, damages, liabilities, and expenses (including attorneys' fees, costs, penalties, interest and disbursements) we incur in relation to, arising from, or for the purpose of avoiding, any claim or demand from a third party relating to your use of the Service or the use of the Service by any person using your account, including any claim that your use of the Service violates any applicable law or regulation, or the rights of any third party, and/or your violation of these Terms.
|
||||
|
||||
## Termination
|
||||
|
||||
If you breach any of these Terms, we have the right to suspend or disable your access to or use of the Service.
|
||||
|
||||
## Entire Agreement
|
||||
|
||||
These Terms constitute the entire agreement between you and **AuroraOSS** regarding the use of the Service, superseding any prior agreements between you and **AuroraOSS** relating to your use of the Service.
|
||||
|
||||
## Feedback
|
||||
|
||||
Please let us know what you think of the Service, these Terms and, in general, **Aurora Store**. When you provide us with any feedback, comments or suggestions about the Service, these Terms and, in general, **Aurora Store**, you irrevocably assign to us all of your right, title and interest in and to your feedback, comments and suggestions.
|
||||
|
||||
If you discover or suspect any Service security breaches, please let us know as soon as possible.
|
||||
|
||||
## Questions & Contact Information
|
||||
|
||||
Questions or comments about the Service may be directed to us at the email address **aurora.dev@gmail.com**.
|
||||
@@ -172,7 +172,7 @@ dependencies {
|
||||
implementation "com.github.topjohnwu.libsu:core:${versions.libsu}"
|
||||
|
||||
//Love <3
|
||||
api("com.gitlab.AuroraOSS:gplayapi:82843a04ce")
|
||||
api("com.gitlab.AuroraOSS:gplayapi:7d7a0deab4")
|
||||
}
|
||||
|
||||
Properties props = new Properties()
|
||||
|
||||
@@ -55,17 +55,20 @@
|
||||
android:name=".AuroraApplication"
|
||||
android:allowBackup="true"
|
||||
android:banner="@drawable/ic_launcher_background"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:killAfterRestore="false"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:restoreAnyVersion="true"
|
||||
android:restoreNeedsApplication="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="m">
|
||||
|
||||
<activity
|
||||
android:name=".view.ui.onboarding.OnboardingActivity">
|
||||
<activity android:name=".view.ui.onboarding.OnboardingActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -82,11 +85,50 @@
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name=".view.ui.details.AppDetailsActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="market.android.com" />
|
||||
<data android:path="/store/apps/details" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="play.google.com" />
|
||||
<data android:path="/store/apps/details" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name=".view.ui.details.DevProfileActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="play.google.com" />
|
||||
<data android:path="/store/apps/dev" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".view.ui.search.SearchSuggestionActivity" />
|
||||
<activity android:name=".view.ui.search.SearchResultsActivity" />
|
||||
<activity android:name=".view.ui.commons.StreamBrowseActivity" />
|
||||
<activity android:name=".view.ui.sale.AppSalesActivity" />
|
||||
<activity android:name=".view.ui.details.AppDetailsActivity" />
|
||||
<activity android:name=".view.ui.details.ScreenshotActivity" />
|
||||
<activity android:name=".view.ui.all.AppsGamesActivity" />
|
||||
<activity android:name=".view.ui.commons.CategoryBrowseActivity" />
|
||||
@@ -101,6 +143,8 @@
|
||||
<activity android:name=".view.ui.commons.BlacklistActivity" />
|
||||
<activity android:name=".view.ui.preferences.SettingsActivity" />
|
||||
<activity android:name=".view.ui.about.AboutActivity" />
|
||||
<activity android:name=".view.ui.commons.ExpandedStreamBrowseActivity" />
|
||||
<activity android:name=".view.ui.commons.EditorStreamBrowseActivity" />
|
||||
|
||||
<service android:name=".data.service.NotificationService" />
|
||||
<service android:name=".data.installer.InstallerService" />
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"id": "0",
|
||||
"title": "Session installer",
|
||||
"subtitle": "Session based installer for bundled/split APKs",
|
||||
"description": "Best suited for devices running Android 5.0+.",
|
||||
"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.",
|
||||
"description": "Best suited for devices running below Android 4.4",
|
||||
"url": "https://developer.android.com/reference/android/content/Intent#setDataAndType"
|
||||
},
|
||||
{
|
||||
|
||||
75
app/src/main/java/com/aurora/extensions/Activity.kt
Normal file
75
app/src/main/java/com/aurora/extensions/Activity.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
|
||||
fun AppCompatActivity.close() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAfterTransition()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
fun AppCompatActivity.restart() {
|
||||
val intent = intent
|
||||
overridePendingTransition(0, 0)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
finish()
|
||||
overridePendingTransition(0, 0)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivity(): Unit =
|
||||
this.startActivity(newIntent<T>())
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivity(flags: Int): Unit =
|
||||
this.startActivity(newIntent<T>(flags))
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivity(extras: Bundle): Unit =
|
||||
this.startActivity(newIntent<T>(extras))
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivity(flags: Int, extras: Bundle): Unit =
|
||||
this.startActivity(newIntent<T>(flags, extras))
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivityForResult(requestCode: Int): Unit =
|
||||
this.startActivityForResult(newIntent<T>(), requestCode)
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivityForResult(
|
||||
requestCode: Int,
|
||||
flags: Int
|
||||
): Unit =
|
||||
this.startActivityForResult(newIntent<T>(flags), requestCode)
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivityForResult(
|
||||
extras: Bundle, requestCode: Int
|
||||
): Unit =
|
||||
this.startActivityForResult(newIntent<T>(extras), requestCode)
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivityForResult(
|
||||
extras: Bundle, requestCode: Int, flags: Int
|
||||
): Unit =
|
||||
this.startActivityForResult(newIntent<T>(flags, extras), requestCode)
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
fun <T> MutableList<T>.flushAndAdd(list: List<T>) {
|
||||
clear()
|
||||
@@ -17,10 +17,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.os.Build
|
||||
import android.text.format.DateFormat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.*
|
||||
|
||||
fun Long.toDate(): String {
|
||||
@@ -29,18 +30,10 @@ fun Long.toDate(): String {
|
||||
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
|
||||
fun Throwable.stackTraceToString(): String {
|
||||
val stringWriter = StringWriter(1024)
|
||||
val printWriter = PrintWriter(stringWriter)
|
||||
printStackTrace(printWriter)
|
||||
printWriter.close()
|
||||
return stringWriter.toString()
|
||||
}
|
||||
32
app/src/main/java/com/aurora/extensions/Conditional.kt
Normal file
32
app/src/main/java/com/aurora/extensions/Conditional.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
inline fun doIf(condition: Boolean?, action: () -> Unit) {
|
||||
if (condition == true) action()
|
||||
}
|
||||
|
||||
inline fun doIf(condition: () -> Boolean?, action: () -> Unit) {
|
||||
if (condition() == true) action()
|
||||
}
|
||||
|
||||
inline fun doIf(any: Any?, action: () -> Unit) {
|
||||
if (any != null) action()
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
@@ -25,18 +25,27 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
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
|
||||
|
||||
val Context.inflater: LayoutInflater
|
||||
get() = LayoutInflater.from(this)
|
||||
|
||||
val Context.displayMetrics: DisplayMetrics
|
||||
get() = resources.displayMetrics
|
||||
|
||||
fun Context.browse(url: String) {
|
||||
try {
|
||||
@@ -82,18 +91,10 @@ fun <T> Context.open(className: Class<T>, newTask: Boolean = false) {
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(
|
||||
intent,
|
||||
ViewUtil.getEmptyActivityBundle(this)
|
||||
getEmptyActivityBundle()
|
||||
)
|
||||
}
|
||||
|
||||
fun AppCompatActivity.close() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAfterTransition()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.restartApp() {
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
@@ -107,8 +108,23 @@ fun Context.restartApp() {
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun Context.getEmptyActivityBundle(): Bundle? {
|
||||
return ActivityOptionsCompat.makeCustomAnimation(
|
||||
this,
|
||||
android.R.anim.fade_in,
|
||||
android.R.anim.fade_out
|
||||
).toBundle()
|
||||
}
|
||||
|
||||
fun Context.copyToClipBoard(data: String?) {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Download Url", data)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
|
||||
fun Context.getStyledAttributeColor(id: Int): Int {
|
||||
val arr = obtainStyledAttributes(TypedValue().data, intArrayOf(id))
|
||||
val styledAttr = arr.getColor(0, Color.WHITE)
|
||||
arr.recycle()
|
||||
return styledAttr
|
||||
}
|
||||
63
app/src/main/java/com/aurora/extensions/Dialog.kt
Normal file
63
app/src/main/java/com/aurora/extensions/Dialog.kt
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
fun Context.showDialog(@StringRes titleId: Int, @StringRes messageId: Int) {
|
||||
runOnUiThread {
|
||||
val backgroundColor: Int = getStyledAttributeColor(android.R.attr.colorBackground)
|
||||
val builder = MaterialAlertDialogBuilder(this).apply {
|
||||
setTitle(titleId)
|
||||
setMessage(messageId)
|
||||
setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _ -> dialog.dismiss() }
|
||||
background = ColorDrawable(backgroundColor)
|
||||
}.create()
|
||||
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.showDialog(title: String?, message: String?) {
|
||||
runOnUiThread {
|
||||
val backgroundColor: Int = getStyledAttributeColor(android.R.attr.colorBackground)
|
||||
val builder = MaterialAlertDialogBuilder(this).apply {
|
||||
setTitle(title)
|
||||
setMessage(message)
|
||||
setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _ -> dialog.dismiss() }
|
||||
background = ColorDrawable(backgroundColor)
|
||||
}.create()
|
||||
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Fragment.showDialog(@StringRes titleId: Int, @StringRes messageId: Int) {
|
||||
requireContext().showDialog(titleId, messageId)
|
||||
}
|
||||
|
||||
fun Fragment.showDialog(title: String, message: String) {
|
||||
requireContext().showDialog(title, message)
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
42
app/src/main/java/com/aurora/extensions/Intent.kt
Normal file
42
app/src/main/java/com/aurora/extensions/Intent.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
|
||||
inline fun <reified T : Context> Context.newIntent(): Intent =
|
||||
Intent(this, T::class.java)
|
||||
|
||||
inline fun <reified T : Context> Context.newIntent(flags: Int): Intent {
|
||||
val intent = newIntent<T>()
|
||||
intent.flags = flags
|
||||
return intent
|
||||
}
|
||||
|
||||
inline fun <reified T : Context> Context.newIntent(extras: Bundle): Intent =
|
||||
newIntent<T>(0, extras)
|
||||
|
||||
inline fun <reified T : Context> Context.newIntent(flags: Int, extras: Bundle): Intent {
|
||||
val intent = newIntent<T>(flags)
|
||||
intent.putExtras(extras)
|
||||
return intent
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.res.Resources
|
||||
|
||||
96
app/src/main/java/com/aurora/extensions/Platform.kt
Normal file
96
app/src/main/java/com/aurora/extensions/Platform.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
|
||||
fun isLAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
}
|
||||
|
||||
fun isMAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
}
|
||||
|
||||
fun isNAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
}
|
||||
|
||||
fun isOAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
}
|
||||
|
||||
fun isPAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
}
|
||||
|
||||
fun isQAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
}
|
||||
|
||||
fun isRAndAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
}
|
||||
|
||||
fun isMIUI(): Boolean {
|
||||
return getSystemProperty("ro.miui.ui.version.name").isNotEmpty()
|
||||
}
|
||||
|
||||
fun isHuawei(): Boolean {
|
||||
return Build.MANUFACTURER.toLowerCase(Locale.getDefault()).contains("huawei")
|
||||
|| Build.HARDWARE.toLowerCase(Locale.getDefault()).contains("kirin")
|
||||
|| Build.HARDWARE.toLowerCase(Locale.getDefault()).contains("hi3")
|
||||
}
|
||||
|
||||
fun getMIUIVersion(): String {
|
||||
val version = getSystemProperty("ro.miui.ui.version.name")
|
||||
val versionCode = getSystemProperty("ro.miui.ui.version.code")
|
||||
return if (version.isNotEmpty() && versionCode.isNotEmpty())
|
||||
"$version.$versionCode"
|
||||
else
|
||||
"unknown"
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
fun isMiuiOptimizationDisabled(): Boolean {
|
||||
if ("0" == getSystemProperty("persist.sys.miui_optimization")) {
|
||||
return true
|
||||
} else try {
|
||||
return Class.forName("android.miui.AppOpsUtils")
|
||||
.getDeclaredMethod("isXOptMode")
|
||||
.invoke(null) as Boolean
|
||||
} catch (e: java.lang.Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
fun getSystemProperty(key: String): String {
|
||||
return try {
|
||||
Class.forName("android.os.SystemProperties")
|
||||
.getDeclaredMethod("get", String::class.java)
|
||||
.invoke(null, key) as String
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
@@ -33,8 +33,6 @@ 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,
|
||||
@@ -140,8 +138,7 @@ private fun AppCompatActivity.setLightStatusBar() {
|
||||
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)
|
||||
window.navigationBarColor = getStyledAttributeColor(android.R.attr.colorBackground)
|
||||
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
}
|
||||
window.decorView.systemUiVisibility = flags
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -25,17 +25,10 @@ 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))
|
||||
when {
|
||||
isMainThread() -> action.invoke()
|
||||
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()
|
||||
private fun isMainThread() = Looper.myLooper() == Looper.getMainLooper()
|
||||
@@ -17,20 +17,32 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
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 =
|
||||
fun Context.toast(resId: Int) = runOnUiThread {
|
||||
Toast.makeText(this, resId, Toast.LENGTH_SHORT).apply { show() }
|
||||
}
|
||||
|
||||
fun Context.longToast(@StringRes resId: Int): Toast =
|
||||
fun Context.toast(text: CharSequence) = runOnUiThread {
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).apply { show() }
|
||||
}
|
||||
|
||||
fun Context.toastLong(resId: Int) = runOnUiThread {
|
||||
Toast.makeText(this, resId, Toast.LENGTH_LONG).apply { show() }
|
||||
}
|
||||
|
||||
fun Context.toastLong(text: CharSequence) = runOnUiThread {
|
||||
Toast.makeText(this, text, Toast.LENGTH_LONG).apply { show() }
|
||||
}
|
||||
|
||||
fun Fragment.toast(resId: Int) = requireContext().toast(resId)
|
||||
|
||||
fun Fragment.toast(text: CharSequence) = requireContext().toast(text)
|
||||
|
||||
fun Fragment.toastLong(resId: Int) = requireContext().toastLong(resId)
|
||||
|
||||
fun Fragment.toastLong(text: CharSequence) = requireContext().toastLong(text)
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.util.extensions
|
||||
package com.aurora.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
@@ -36,6 +36,10 @@ class AuroraApplication : MultiDexApplication() {
|
||||
private lateinit var fetch: Fetch
|
||||
private lateinit var packageManagerReceiver: PackageManagerReceiver
|
||||
|
||||
companion object{
|
||||
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ package com.aurora.store
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -36,15 +39,14 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.Navigation
|
||||
import androidx.navigation.ui.NavigationUI
|
||||
import com.aurora.extensions.getEmptyActivityBundle
|
||||
import com.aurora.extensions.getStyledAttributeColor
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.open
|
||||
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
|
||||
@@ -68,6 +70,18 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
private var lastBackPressed = 0L
|
||||
|
||||
override fun onConnected() {
|
||||
hideNetworkConnectivitySheet()
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
showNetworkConnectivitySheet()
|
||||
}
|
||||
|
||||
override fun onReconnected() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private fun matchDestination(
|
||||
@@ -97,16 +111,20 @@ class MainActivity : BaseActivity() {
|
||||
attachSearch()
|
||||
|
||||
checkPermission()
|
||||
checkStoragePermission()
|
||||
}
|
||||
|
||||
private fun checkPermission() = runWithPermissions(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) {
|
||||
Log.i("External Storage Access Available")
|
||||
Log.i("Required permissions available")
|
||||
}
|
||||
|
||||
if (isQAndAbove()) {
|
||||
//pickExternalFileDir()
|
||||
private fun checkStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager())
|
||||
startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +134,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
B.viewToolbar.imgActionSecondary.setOnClickListener {
|
||||
val userAppIntent = Intent(this, DownloadActivity::class.java)
|
||||
startActivity(userAppIntent, ViewUtil.getEmptyActivityBundle(this))
|
||||
open(DownloadActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +142,7 @@ class MainActivity : BaseActivity() {
|
||||
B.searchFab.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(this, SearchSuggestionActivity::class.java),
|
||||
ViewUtil.getEmptyActivityBundle(this)
|
||||
getEmptyActivityBundle()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -134,7 +151,7 @@ class MainActivity : BaseActivity() {
|
||||
val bottomNavigationView: BottomNavigationView = B.navView
|
||||
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
|
||||
|
||||
val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground)
|
||||
val backGroundColor = getStyledAttributeColor(android.R.attr.colorBackground)
|
||||
bottomNavigationView.setBackgroundColor(ColorUtils.setAlphaComponent(backGroundColor, 245))
|
||||
|
||||
|
||||
@@ -215,18 +232,6 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnected() {
|
||||
hideNetworkConnectivitySheet()
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
showNetworkConnectivitySheet()
|
||||
}
|
||||
|
||||
override fun onReconnected() {
|
||||
|
||||
}
|
||||
|
||||
private fun pickExternalFileDir() {
|
||||
val getContentIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
getContentIntent.type = "*/*"
|
||||
|
||||
@@ -20,12 +20,37 @@
|
||||
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 InstallEvent(
|
||||
var packageName: String,
|
||||
var extra: String? = ""
|
||||
) : BusEvent()
|
||||
|
||||
data class UninstallEvent(
|
||||
var packageName: String,
|
||||
var extra: String? = ""
|
||||
) : BusEvent()
|
||||
|
||||
data class Blacklisted(
|
||||
var packageName: String,
|
||||
var error: String? = ""
|
||||
) : BusEvent()
|
||||
|
||||
data class GoogleAAS(
|
||||
var success: Boolean,
|
||||
var email: String = String(),
|
||||
var aasToken: String = String()
|
||||
) : BusEvent()
|
||||
}
|
||||
|
||||
sealed class InstallerEvent {
|
||||
data class Success(
|
||||
var packageName: String? = "",
|
||||
var extra: String? = ""
|
||||
) : InstallerEvent()
|
||||
|
||||
data class Failed(
|
||||
var packageName: String? = "",
|
||||
var error: String? = "",
|
||||
var extra: String? = ""
|
||||
) : InstallerEvent()
|
||||
}
|
||||
@@ -25,9 +25,7 @@ import com.aurora.store.data.SingletonHolder
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
|
||||
open class AppInstaller private constructor(var context: Context) {
|
||||
|
||||
companion object : SingletonHolder<AppInstaller, Context>(::AppInstaller)
|
||||
open class AppInstaller constructor(var context: Context) {
|
||||
|
||||
fun getPreferredInstaller(): IInstaller {
|
||||
val prefValue = Preferences.getInteger(
|
||||
|
||||
@@ -24,23 +24,22 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.store.AuroraApplication
|
||||
import com.aurora.store.BuildConfig
|
||||
import java.io.File
|
||||
|
||||
abstract class InstallerBase(protected var context: Context) : IInstaller {
|
||||
|
||||
private val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
|
||||
override fun clearQueue() {
|
||||
enqueuedInstalls.clear()
|
||||
AuroraApplication.enqueuedInstalls.clear()
|
||||
}
|
||||
|
||||
override fun isAlreadyQueued(packageName: String): Boolean {
|
||||
return enqueuedInstalls.contains(packageName)
|
||||
return AuroraApplication.enqueuedInstalls.contains(packageName)
|
||||
}
|
||||
|
||||
override fun removeFromInstallQueue(packageName: String) {
|
||||
enqueuedInstalls.remove(packageName)
|
||||
AuroraApplication.enqueuedInstalls.remove(packageName)
|
||||
}
|
||||
|
||||
override fun uninstall(packageName: String) {
|
||||
|
||||
@@ -25,52 +25,69 @@ import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.util.Log
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class InstallerService : Service() {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -69)
|
||||
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||
|
||||
//Send broadcast for the installation status of the package
|
||||
sendStatusBroadcast(status, packageName)
|
||||
|
||||
//Launch user confirmation activity
|
||||
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
confirmationIntent!!.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
confirmationIntent.putExtra(
|
||||
Intent.EXTRA_INSTALLER_PACKAGE_NAME,
|
||||
"com.android.vending"
|
||||
)
|
||||
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
startActivity(confirmationIntent)
|
||||
} catch (e: Exception) {
|
||||
sendStatusBroadcast(PackageInstaller.STATUS_FAILURE, packageName)
|
||||
}
|
||||
val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> promptUser(intent)
|
||||
else -> postStatus(status, packageName, extra)
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun sendStatusBroadcast(status: Int, packageName: String?) {
|
||||
if (StringUtils.isNotEmpty(packageName)) {
|
||||
val statusIntent = Intent(ACTION_SESSION_INSTALLER)
|
||||
statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status)
|
||||
statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
|
||||
sendBroadcast(statusIntent)
|
||||
private fun promptUser(intent: Intent) {
|
||||
val confirmationIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
|
||||
confirmationIntent?.let {
|
||||
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
|
||||
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
try {
|
||||
startActivity(it)
|
||||
} catch (e: Exception) {
|
||||
Log.e(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun postStatus(status: Int, packageName: String?, extra: String?) {
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
EventBus.getDefault().post(InstallerEvent.Success(packageName, "Success"))
|
||||
}
|
||||
else -> {
|
||||
val errorString = getErrorString(status)
|
||||
Log.e("$packageName : $errorString")
|
||||
EventBus.getDefault().post(InstallerEvent.Failed(packageName, errorString, extra))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorString(status: Int): String {
|
||||
return when (status) {
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> getString(R.string.installer_status_user_action)
|
||||
PackageInstaller.STATUS_FAILURE_BLOCKED -> getString(R.string.installer_status_failure_blocked)
|
||||
PackageInstaller.STATUS_FAILURE_CONFLICT -> getString(R.string.installer_status_failure_conflict)
|
||||
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> getString(R.string.installer_status_failure_incompatible)
|
||||
PackageInstaller.STATUS_FAILURE_INVALID -> getString(R.string.installer_status_failure_invalid)
|
||||
PackageInstaller.STATUS_FAILURE_STORAGE -> getString(R.string.installer_status_failure_storage)
|
||||
else -> getString(R.string.installer_status_failure)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER"
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,12 @@
|
||||
package com.aurora.store.data.installer
|
||||
|
||||
import android.content.Context
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
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 org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -50,7 +51,12 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
xInstallLegacy(packageName, it)
|
||||
}
|
||||
} else {
|
||||
context.toast(context.getString(R.string.installer_root_unavailable))
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_root_unavailable)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
||||
}
|
||||
}
|
||||
@@ -80,12 +86,34 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
.exec()
|
||||
}
|
||||
|
||||
Shell.su("pm install-commit $sessionId").exec()
|
||||
val shellResult = Shell.su("pm install-commit $sessionId").exec()
|
||||
|
||||
if (!shellResult.isSuccess) {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
parseError(shellResult)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_root_unavailable)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_status_failure_session)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +124,8 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseError(result: Shell.Result): String {
|
||||
return result.err.joinToString(separator = "\n")
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,15 @@ import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.services.IPrivilegedCallback
|
||||
import com.aurora.services.IPrivilegedService
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
@@ -44,47 +49,81 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun install(packageName: String, files: List<Any>) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
isAlreadyQueued(packageName) -> {
|
||||
Log.i("$packageName already queued")
|
||||
}
|
||||
|
||||
xInstall(packageName, uriList)
|
||||
PackageUtil.isInstalled(context, PRIVILEGED_EXTENSION_PACKAGE_NAME) -> {
|
||||
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)
|
||||
}
|
||||
else -> {
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_unavailable)
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
||||
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.installSplitPackage(
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
Log.e("$packageName : $returnCode")
|
||||
removeFromInstallQueue(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.installSplitPackage(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
EventBus
|
||||
.getDefault()
|
||||
.post(
|
||||
InstallerEvent.Failed(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
)
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
}
|
||||
} else {
|
||||
Log.e(context.getString(R.string.installer_service_misconfigured))
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_misconfigured)
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +142,20 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUri(file: File): Uri {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${BuildConfig.APPLICATION_ID}.fileProvider",
|
||||
file
|
||||
)
|
||||
|
||||
context.grantUriPermission(
|
||||
PRIVILEGED_EXTENSION_PACKAGE_NAME,
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
|
||||
return uri
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,12 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.extensions.isNAndAbove
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.util.Log
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
@@ -57,18 +60,22 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(packageName)
|
||||
if (isNAndAbove()) {
|
||||
setOriginatingUid(android.os.Process.myUid())
|
||||
}
|
||||
}
|
||||
val sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
|
||||
try {
|
||||
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
var apkId = 1
|
||||
|
||||
for (uri in uriList) {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val outputStream = session.openWrite(
|
||||
"${packageName}_${apkId++}",
|
||||
"${packageName}_${System.currentTimeMillis()}",
|
||||
0,
|
||||
-1
|
||||
)
|
||||
@@ -81,11 +88,11 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
IOUtils.close(outputStream)
|
||||
}
|
||||
|
||||
val intent = Intent(context, InstallerService::class.java)
|
||||
val callBackIntent = Intent(context, InstallerService::class.java)
|
||||
val pendingIntent = PendingIntent.getService(
|
||||
context,
|
||||
sessionId,
|
||||
intent,
|
||||
callBackIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
@@ -95,6 +102,12 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
} catch (e: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
EventBus.getDefault().post(event)
|
||||
Log.e("Failed to install $packageName : %s", e.message)
|
||||
}
|
||||
}
|
||||
@@ -106,13 +119,11 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
file
|
||||
)
|
||||
|
||||
uri.apply {
|
||||
context.grantUriPermission(
|
||||
BuildConfig.APPLICATION_ID,
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
context.grantUriPermission(
|
||||
BuildConfig.APPLICATION_ID,
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.model
|
||||
|
||||
data class AppPermission(
|
||||
var name: String,
|
||||
var permission: String,
|
||||
var raw: String,
|
||||
var flags: Int,
|
||||
var icon: Int
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return when (other) {
|
||||
is AppPermission -> other.permission == permission
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return permission.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,15 @@
|
||||
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) {
|
||||
class SpoofProvider constructor(var context: Context) {
|
||||
|
||||
companion object : SingletonHolder<SpoofProvider, Context>(::SpoofProvider) {
|
||||
companion object {
|
||||
const val LOCALE_SPOOF_ENABLED = "LOCALE_SPOOF_ENABLED"
|
||||
const val LOCALE_SPOOF_LANG = "LOCALE_SPOOF_LANG"
|
||||
const val LOCALE_SPOOF_COUNTRY = "LOCALE_SPOOF_COUNTRY"
|
||||
|
||||
@@ -28,6 +28,7 @@ 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 com.aurora.store.util.isExternalStorageEnable
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
@@ -39,19 +40,20 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
|
||||
when (intent.action) {
|
||||
Intent.ACTION_PACKAGE_ADDED -> {
|
||||
EventBus.getDefault()
|
||||
.post(InstallEvent(packageName, ""))
|
||||
|
||||
//Clear installation queue
|
||||
AppInstaller.with(context)
|
||||
.getPreferredInstaller()
|
||||
.removeFromInstallQueue(packageName)
|
||||
EventBus.getDefault().post(InstallEvent(packageName, ""))
|
||||
}
|
||||
|
||||
Intent.ACTION_PACKAGE_REMOVED -> {
|
||||
EventBus.getDefault().post(UninstallEvent(packageName, ""))
|
||||
}
|
||||
Intent.ACTION_PACKAGE_REMOVED -> EventBus.getDefault()
|
||||
.post(UninstallEvent(packageName, ""))
|
||||
}
|
||||
|
||||
clearNotification(context, packageName)
|
||||
//Clear installation queue
|
||||
AppInstaller(context)
|
||||
.getPreferredInstaller()
|
||||
.removeFromInstallQueue(packageName)
|
||||
|
||||
//clearNotification(context, packageName)
|
||||
|
||||
val isAutoDeleteAPKEnabled = Preferences.getBoolean(
|
||||
context,
|
||||
|
||||
@@ -29,9 +29,12 @@ import android.util.ArrayMap
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.getStyledAttributeColor
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.receiver.DownloadCancelReceiver
|
||||
import com.aurora.store.data.receiver.DownloadPauseReceiver
|
||||
@@ -39,13 +42,14 @@ 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 org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
|
||||
@@ -83,6 +87,8 @@ class NotificationService : Service() {
|
||||
|
||||
Log.i("Notification Service Started")
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
//Create Notification Channels : General & Alert
|
||||
@@ -97,9 +103,6 @@ class NotificationService : Service() {
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
showNotification(groupId, download, fetchGroup)
|
||||
if (fetchGroup.groupDownloadProgress == 100) {
|
||||
install(download.tag!!, fetchGroup.downloads)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(
|
||||
@@ -214,8 +217,8 @@ class NotificationService : Service() {
|
||||
}
|
||||
val progress = fetchGroup.groupDownloadProgress
|
||||
val progressBigText = NotificationCompat.BigTextStyle()
|
||||
when (status) {
|
||||
|
||||
when (status) {
|
||||
Status.QUEUED -> {
|
||||
builder.setProgress(100, 0, true)
|
||||
progressBigText.bigText(getString(R.string.download_queued))
|
||||
@@ -308,7 +311,6 @@ class NotificationService : Service() {
|
||||
app.id,
|
||||
builder.build()
|
||||
)
|
||||
//}
|
||||
}
|
||||
|
||||
private fun getPauseIntent(groupId: Int): PendingIntent {
|
||||
@@ -356,9 +358,28 @@ class NotificationService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
@Subscribe()
|
||||
fun onEventMainThread(event: Any) {
|
||||
when (event) {
|
||||
is InstallerEvent.Success -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.extra)
|
||||
}
|
||||
is InstallerEvent.Failed -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.error)
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun install(packageName: String, files: List<Download>) {
|
||||
AppInstaller.with(this)
|
||||
AppInstaller(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
packageName,
|
||||
@@ -370,9 +391,25 @@ class NotificationService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
private fun notifyInstallationStatus(app: App, status: String?) {
|
||||
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_ALERT)
|
||||
builder.color = getStyledAttributeColor(R.attr.colorAccent)
|
||||
builder.setSmallIcon(R.drawable.ic_install)
|
||||
builder.setContentTitle(app.displayName)
|
||||
builder.setContentText(status)
|
||||
builder.setSubText(app.packageName)
|
||||
|
||||
notificationManager.notify(
|
||||
app.packageName,
|
||||
app.id,
|
||||
builder.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("Notification Service Stopped")
|
||||
fetch.removeListener(fetchListener)
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class AC2DMTask {
|
||||
.response()
|
||||
|
||||
return response.third.fold(success = {
|
||||
Util.parseResponse(String(it))
|
||||
AC2DMUtil.parseResponse(String(it))
|
||||
}, failure = {
|
||||
mapOf()
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Util {
|
||||
public class AC2DMUtil {
|
||||
|
||||
public static Map<String, String> parseResponse(String response) {
|
||||
Map<String, String> keyValueMap = new HashMap<String, String>();
|
||||
@@ -22,12 +22,9 @@ 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 com.aurora.extensions.isLAndAbove
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -111,12 +108,4 @@ class ApkCopier(private val context: Context, private val packageName: String) {
|
||||
Log.e("ApkCopier : %s", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun getUri(file: File): Uri {
|
||||
return FileProvider.getUriForFile(
|
||||
context,
|
||||
"${BuildConfig.APPLICATION_ID}.fileProvider",
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package com.aurora.store.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.aurora.store.util.extensions.isPAndAbove
|
||||
import com.aurora.extensions.isPAndAbove
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.cert.CertificateFactory
|
||||
@@ -35,22 +35,24 @@ object CertUtil {
|
||||
private fun getX509Certificates(
|
||||
context: Context,
|
||||
packageName: String
|
||||
): List<X509Certificate?> {
|
||||
val certificates: MutableList<X509Certificate?> = mutableListOf()
|
||||
): List<X509Certificate> {
|
||||
val certificates: MutableList<X509Certificate> = mutableListOf()
|
||||
val packageManager = context.applicationContext.packageManager
|
||||
|
||||
try {
|
||||
|
||||
val packageInfo = if (isPAndAbove())
|
||||
val packageInfo = if (isPAndAbove()) {
|
||||
packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES
|
||||
)
|
||||
else
|
||||
}
|
||||
else {
|
||||
packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNATURES
|
||||
)
|
||||
}
|
||||
|
||||
val certificateFactory = CertificateFactory.getInstance("X509")
|
||||
|
||||
@@ -63,7 +65,7 @@ object CertUtil {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for (i in 0..packageInfo.signatures.size) {
|
||||
for (i in packageInfo.signatures.indices) {
|
||||
val bytes = packageInfo.signatures[i].toByteArray()
|
||||
val inStream: InputStream = ByteArrayInputStream(bytes)
|
||||
certificates.add(
|
||||
@@ -72,7 +74,7 @@ object CertUtil {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(e.message)
|
||||
|
||||
}
|
||||
|
||||
return certificates
|
||||
@@ -85,13 +87,10 @@ object CertUtil {
|
||||
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
|
||||
}
|
||||
|
||||
if (cert.subjectDN != null) {
|
||||
val DN = cert.subjectDN.name.toUpperCase(Locale.getDefault())
|
||||
DN.contains(FDROID) || DN.contains(GUARDIAN)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ 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.*
|
||||
@@ -37,6 +35,7 @@ object CommonUtil {
|
||||
Pair(6, " MB"),
|
||||
Pair(9, " GB")
|
||||
)
|
||||
|
||||
private val diPrefixes: Map<Int, String> = hashMapOf(
|
||||
Pair(1, ""),
|
||||
Pair(3, " K"),
|
||||
@@ -134,14 +133,6 @@ object CommonUtil {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -51,6 +51,19 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstalledVersion(context: Context, packageName: String): String {
|
||||
return try {
|
||||
val packageInfo = getPackageInfo(context, packageName)
|
||||
if (packageInfo != null) {
|
||||
"${packageInfo.versionName}.${packageInfo.versionCode}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun isTv(context: Context): Boolean {
|
||||
val uiMode = context.resources.configuration.uiMode
|
||||
return uiMode and Configuration.UI_MODE_TYPE_MASK == Configuration.UI_MODE_TYPE_TELEVISION
|
||||
@@ -111,7 +124,7 @@ object PackageUtil {
|
||||
}
|
||||
|
||||
if (isFdroidFilterEnabled) {
|
||||
packageInfoList
|
||||
packageInfoList = packageInfoList
|
||||
.filter {
|
||||
val packageInstaller = packageManager.getInstallerPackageName(it.packageName)
|
||||
packageInstaller != "org.fdroid.fdroid.privileged"
|
||||
|
||||
@@ -21,9 +21,9 @@ package com.aurora.store.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
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
|
||||
@@ -32,14 +32,18 @@ fun Context.getInternalBaseDirectory(): String {
|
||||
object PathUtil {
|
||||
|
||||
private fun getDownloadDirectory(context: Context): String {
|
||||
return if (isLAndAbove())
|
||||
context.getInternalBaseDirectory() + "/Downloads"
|
||||
else
|
||||
return if (isLAndAbove()) {
|
||||
if (context.isExternalStorageEnable())
|
||||
getExternalPath()
|
||||
else
|
||||
context.getInternalBaseDirectory() + "/Downloads"
|
||||
} else {
|
||||
getExternalPath()
|
||||
}
|
||||
}
|
||||
|
||||
fun getPackageDirectory(context: Context, packageName: String): String {
|
||||
return getDownloadDirectory(context) + "/$packageName"
|
||||
return getDownloadDirectory(context) + "/Downloads/$packageName"
|
||||
}
|
||||
|
||||
private fun getVersionDirectory(
|
||||
@@ -59,11 +63,11 @@ object PathUtil {
|
||||
}
|
||||
|
||||
fun getExternalPath(): String {
|
||||
return Environment.getExternalStorageDirectory().toString() + "/Aurora/"
|
||||
return Environment.getExternalStorageDirectory().toString() + "/Aurora/Store"
|
||||
}
|
||||
|
||||
fun getBaseCopyDirectory(): String {
|
||||
return "${getExternalPath()}/files/export/"
|
||||
return "${getExternalPath()}/Exports/"
|
||||
}
|
||||
|
||||
private fun getObbDownloadPath(context: Context, app: App): String {
|
||||
@@ -75,4 +79,8 @@ object PathUtil {
|
||||
val obbDir = getObbDownloadPath(context, app)
|
||||
return "$obbDir/${file.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isExternalStorageEnable(): Boolean {
|
||||
return Preferences.getBoolean(this, Preferences.PREFERENCE_DOWNLOAD_EXTERNAL)
|
||||
}
|
||||
@@ -43,6 +43,7 @@ object Preferences {
|
||||
const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION"
|
||||
|
||||
const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE"
|
||||
const val PREFERENCE_DOWNLOAD_EXTERNAL = "PREFERENCE_DOWNLOAD_EXTERNAL"
|
||||
const val PREFERENCE_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI"
|
||||
|
||||
|
||||
@@ -91,26 +92,17 @@ object Preferences {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.save(key: String, value: Int) {
|
||||
Preferences.putInteger(this, key, value)
|
||||
}
|
||||
/*Preference Extensions*/
|
||||
|
||||
fun Fragment.save(key: String, value: Int) {
|
||||
Preferences.putInteger(requireContext(), key, value)
|
||||
}
|
||||
fun Context.save(key: String, value: Int) = Preferences.putInteger(this, key, value)
|
||||
|
||||
fun Context.save(key: String, value: Boolean) {
|
||||
Preferences.putBoolean(this, 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 Context.save(key: String, value: String) {
|
||||
Preferences.putString(this, key, value)
|
||||
}
|
||||
|
||||
fun Fragment.save(key: String, value: String) {
|
||||
Preferences.putString(requireContext(), key, value)
|
||||
}
|
||||
fun Fragment.save(key: String, value: Int) = requireContext().save(key, value)
|
||||
|
||||
fun Fragment.save(key: String, value: Boolean) = requireContext().save(key, value)
|
||||
|
||||
fun Fragment.save(key: String, value: String) = requireContext().save(key, value)
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.aurora.store.view.custom.layouts
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PermissionGroupInfo
|
||||
import android.content.pm.PermissionInfo
|
||||
import android.content.res.Resources.NotFoundException
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.extensions.showDialog
|
||||
import com.aurora.store.R
|
||||
import java.util.*
|
||||
|
||||
class PermissionGroup : LinearLayout {
|
||||
|
||||
private lateinit var permissionGroupInfo: PermissionGroupInfo
|
||||
private lateinit var packageManager: PackageManager
|
||||
|
||||
private val permissionMap: MutableMap<String, String> = HashMap()
|
||||
|
||||
constructor(context: Context?) : super(context) {
|
||||
init()
|
||||
}
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
||||
init()
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
) {
|
||||
init()
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int,
|
||||
defStyleRes: Int
|
||||
) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
init()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
inflate(context, R.layout.view_permission, this)
|
||||
packageManager = context.packageManager
|
||||
}
|
||||
|
||||
fun setPermissionGroupInfo(permissionGroupInfo: PermissionGroupInfo) {
|
||||
this.permissionGroupInfo = permissionGroupInfo
|
||||
val imageView = findViewById<ImageView>(R.id.img)
|
||||
imageView.setImageDrawable(getPermissionGroupIcon(permissionGroupInfo))
|
||||
//imageView.setColorFilter(getContext().getStyledAttributeColor(imageView.getContext(), android.R.attr.colorAccent));
|
||||
}
|
||||
|
||||
fun addPermission(permissionInfo: PermissionInfo) {
|
||||
val title = permissionInfo.loadLabel(packageManager)
|
||||
val description = permissionInfo.loadDescription(packageManager)
|
||||
|
||||
permissionMap[getReadableLabel(title.toString(), permissionInfo.packageName)] =
|
||||
if (description.isNullOrEmpty())
|
||||
"No description"
|
||||
else
|
||||
description.toString()
|
||||
|
||||
val permissionLabels: List<String> = ArrayList(permissionMap.keys)
|
||||
val permissionLabelsView = findViewById<LinearLayout>(R.id.permission_labels)
|
||||
permissionLabelsView.removeAllViews()
|
||||
|
||||
permissionLabels
|
||||
.filter { it.isNotEmpty() }
|
||||
.sortedBy { it }
|
||||
.forEach {
|
||||
addPermissionLabel(
|
||||
permissionLabelsView,
|
||||
it,
|
||||
permissionMap[it]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addPermissionLabel(
|
||||
permissionLabelsView: LinearLayout,
|
||||
label: String,
|
||||
description: String?
|
||||
) {
|
||||
val textView = TextView(context)
|
||||
textView.text = label
|
||||
textView.setOnClickListener {
|
||||
var title: String = permissionGroupInfo.loadLabel(packageManager).toString()
|
||||
|
||||
if (title.contains("UNDEFINED")) {
|
||||
title = "Android"
|
||||
}
|
||||
|
||||
title = title.capitalize(Locale.getDefault())
|
||||
|
||||
context.showDialog(title, description)
|
||||
}
|
||||
|
||||
permissionLabelsView.addView(textView)
|
||||
}
|
||||
|
||||
private fun getPermissionGroupIcon(permissionGroupInfo: PermissionGroupInfo?): Drawable? {
|
||||
return try {
|
||||
ContextCompat.getDrawable(context, permissionGroupInfo!!.icon)
|
||||
} catch (e: NotFoundException) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||
permissionGroupInfo!!.loadUnbadgedIcon(packageManager)
|
||||
else
|
||||
permissionGroupInfo!!.loadIcon(packageManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReadableLabel(label: String, packageName: String): String {
|
||||
val prefixes: MutableList<String> = mutableListOf(
|
||||
"android",
|
||||
packageName
|
||||
)
|
||||
|
||||
if (label.contains("UNDEFINED")) {
|
||||
return "Android"
|
||||
}
|
||||
|
||||
prefixes
|
||||
.map { "$it.permission." }
|
||||
.forEach {
|
||||
if (label.startsWith(it)) {
|
||||
return it.replace(it, "")
|
||||
.replace("_", " ")
|
||||
.toLowerCase(Locale.getDefault())
|
||||
.capitalize(Locale.getDefault())
|
||||
}
|
||||
}
|
||||
|
||||
return label.capitalize(Locale.getDefault())
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,22 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.custom
|
||||
package com.aurora.store.view.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewActionButtonBinding
|
||||
import nl.komponents.kovenant.task
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ActionButton : RelativeLayout {
|
||||
|
||||
@@ -64,23 +70,70 @@ class ActionButton : RelativeLayout {
|
||||
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton)
|
||||
val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText)
|
||||
|
||||
val btnTxtColor = typedArray.getResourceId(
|
||||
R.styleable.ActionButton_btnActionTextColor,
|
||||
R.color.colorWhite
|
||||
)
|
||||
|
||||
val stateIcon = typedArray.getResourceId(
|
||||
R.styleable.ActionButton_btnActionIcon,
|
||||
R.drawable.ic_check
|
||||
)
|
||||
|
||||
val stateColor = ContextCompat.getColor(context, btnTxtColor)
|
||||
|
||||
B.btn.text = btnTxt
|
||||
B.btn.setTextColor(stateColor)
|
||||
B.img.setImageDrawable(ContextCompat.getDrawable(context, stateIcon))
|
||||
if (isLAndAbove()) {
|
||||
B.img.imageTintList = ColorStateList.valueOf(stateColor)
|
||||
}
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
fun setText(text: String) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btn.text = text
|
||||
}
|
||||
|
||||
fun updateProgress(isVisible: Boolean) {
|
||||
if (isVisible)
|
||||
B.progress.visibility = View.VISIBLE
|
||||
else
|
||||
fun setText(text: Int) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btn.text = getString(text)
|
||||
}
|
||||
|
||||
B.progress.visibility = View.INVISIBLE
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.IDLE -> 0
|
||||
State.PROGRESS -> 1
|
||||
State.COMPLETE -> 2
|
||||
}
|
||||
|
||||
if (B.viewFlipper.displayedChild != displayChild) {
|
||||
runOnUiThread {
|
||||
B.viewFlipper.displayedChild = displayChild
|
||||
if (displayChild == 2)
|
||||
switchToIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToIdle() {
|
||||
task {
|
||||
TimeUnit.SECONDS.sleep(3)
|
||||
} success {
|
||||
updateState(State.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnClickListener(onClickListener: OnClickListener) {
|
||||
B.btn.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.custom
|
||||
package com.aurora.store.view.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
@@ -0,0 +1,398 @@
|
||||
package com.aurora.store.view.custom.progress;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.aurora.store.R;
|
||||
import com.aurora.store.view.custom.progress.indicators.BallPulseIndicator;
|
||||
import com.aurora.store.view.custom.progress.indicators.Indicator;
|
||||
|
||||
|
||||
public class AuroraProgressView extends View {
|
||||
|
||||
private static final String TAG = "AuroraProgressView";
|
||||
|
||||
private static final BallPulseIndicator DEFAULT_INDICATOR = new BallPulseIndicator();
|
||||
|
||||
private static final int MIN_SHOW_TIME = 500; // ms
|
||||
private static final int MIN_DELAY = 500; // ms
|
||||
|
||||
private long startTime = -1;
|
||||
|
||||
private boolean postedHide = false;
|
||||
private boolean postedShow = false;
|
||||
private boolean dismissed = false;
|
||||
|
||||
private final Runnable mDelayedHide = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
postedHide = false;
|
||||
startTime = -1;
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mDelayedShow = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
postedShow = false;
|
||||
if (!dismissed) {
|
||||
startTime = System.currentTimeMillis();
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int minWidth;
|
||||
int maxWidth;
|
||||
int minHeight;
|
||||
int maxHeight;
|
||||
|
||||
private Indicator indicator;
|
||||
private int indicatorColor;
|
||||
|
||||
private boolean mShouldStartAnimationDrawable;
|
||||
|
||||
public AuroraProgressView(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public AuroraProgressView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
public AuroraProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AuroraProgressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(context, attrs, defStyleAttr, R.style.AuroraProgressView);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
minWidth = 24;
|
||||
maxWidth = 48;
|
||||
minHeight = 24;
|
||||
maxHeight = 48;
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.AuroraProgressView, defStyleAttr, defStyleRes);
|
||||
|
||||
minWidth = a.getDimensionPixelSize(R.styleable.AuroraProgressView_minWidth, minWidth);
|
||||
maxWidth = a.getDimensionPixelSize(R.styleable.AuroraProgressView_maxWidth, maxWidth);
|
||||
minHeight = a.getDimensionPixelSize(R.styleable.AuroraProgressView_minHeight, minHeight);
|
||||
maxHeight = a.getDimensionPixelSize(R.styleable.AuroraProgressView_maxHeight, maxHeight);
|
||||
String indicatorName = a.getString(R.styleable.AuroraProgressView_indicatorName);
|
||||
indicatorColor = a.getColor(R.styleable.AuroraProgressView_indicatorColor, Color.WHITE);
|
||||
setIndicator(indicatorName);
|
||||
|
||||
if (indicator == null) {
|
||||
setIndicator(DEFAULT_INDICATOR);
|
||||
}
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public Indicator getIndicator() {
|
||||
return indicator;
|
||||
}
|
||||
|
||||
public void setIndicator(Indicator d) {
|
||||
if (indicator != d) {
|
||||
if (indicator != null) {
|
||||
indicator.setCallback(null);
|
||||
unscheduleDrawable(indicator);
|
||||
}
|
||||
|
||||
indicator = d;
|
||||
//need to set indicator color again if you didn't specified when you update the indicator .
|
||||
setIndicatorColor(indicatorColor);
|
||||
if (d != null) {
|
||||
d.setCallback(this);
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setIndicatorColor(int color) {
|
||||
this.indicatorColor = color;
|
||||
indicator.setColor(color);
|
||||
}
|
||||
|
||||
public void setIndicator(String indicatorName) {
|
||||
if (TextUtils.isEmpty(indicatorName)) {
|
||||
return;
|
||||
}
|
||||
StringBuilder drawableClassName = new StringBuilder();
|
||||
if (!indicatorName.contains(".")) {
|
||||
String defaultPackageName = getClass().getPackage().getName();
|
||||
drawableClassName.append(defaultPackageName)
|
||||
.append(".indicators")
|
||||
.append(".");
|
||||
}
|
||||
drawableClassName.append(indicatorName);
|
||||
try {
|
||||
Class<?> drawableClass = Class.forName(drawableClassName.toString());
|
||||
Indicator indicator = (Indicator) drawableClass.newInstance();
|
||||
setIndicator(indicator);
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG, "Didn't find your class , check the name again !");
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void smoothToShow() {
|
||||
startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_in));
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
public void smoothToHide() {
|
||||
startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));
|
||||
setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
dismissed = true;
|
||||
removeCallbacks(mDelayedShow);
|
||||
long diff = System.currentTimeMillis() - startTime;
|
||||
if (diff >= MIN_SHOW_TIME || startTime == -1) {
|
||||
// The progress spinner has been shown long enough
|
||||
// OR was not shown yet. If it wasn't shown yet,
|
||||
// it will just never be shown.
|
||||
setVisibility(View.GONE);
|
||||
} else {
|
||||
// The progress spinner is shown, but not long enough,
|
||||
// so put a delayed message in to hide it when its been
|
||||
// shown long enough.
|
||||
if (!postedHide) {
|
||||
postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
|
||||
postedHide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void show() {
|
||||
// Reset the start time.
|
||||
startTime = -1;
|
||||
dismissed = false;
|
||||
removeCallbacks(mDelayedHide);
|
||||
if (!postedShow) {
|
||||
postDelayed(mDelayedShow, MIN_DELAY);
|
||||
postedShow = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(Drawable who) {
|
||||
return who == indicator
|
||||
|| super.verifyDrawable(who);
|
||||
}
|
||||
|
||||
void startAnimation() {
|
||||
if (getVisibility() != VISIBLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indicator != null) {
|
||||
mShouldStartAnimationDrawable = true;
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
void stopAnimation() {
|
||||
if (indicator != null) {
|
||||
indicator.stop();
|
||||
mShouldStartAnimationDrawable = false;
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int v) {
|
||||
if (getVisibility() != v) {
|
||||
super.setVisibility(v);
|
||||
if (v == GONE || v == INVISIBLE) {
|
||||
stopAnimation();
|
||||
} else {
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (visibility == GONE || visibility == INVISIBLE) {
|
||||
stopAnimation();
|
||||
} else {
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable dr) {
|
||||
if (verifyDrawable(dr)) {
|
||||
final Rect dirty = dr.getBounds();
|
||||
final int scrollX = getScrollX() + getPaddingLeft();
|
||||
final int scrollY = getScrollY() + getPaddingTop();
|
||||
|
||||
invalidate(dirty.left + scrollX, dirty.top + scrollY,
|
||||
dirty.right + scrollX, dirty.bottom + scrollY);
|
||||
} else {
|
||||
super.invalidateDrawable(dr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
updateDrawableBounds(w, h);
|
||||
}
|
||||
|
||||
private void updateDrawableBounds(int w, int h) {
|
||||
// onDraw will translate the canvas so we draw starting at 0,0.
|
||||
// Subtract out padding for the purposes of the calculations below.
|
||||
w -= getPaddingRight() + getPaddingLeft();
|
||||
h -= getPaddingTop() + getPaddingBottom();
|
||||
|
||||
int right = w;
|
||||
int bottom = h;
|
||||
int top = 0;
|
||||
int left = 0;
|
||||
|
||||
if (indicator != null) {
|
||||
// Maintain aspect ratio. Certain kinds of animated drawables
|
||||
// get very confused otherwise.
|
||||
final int intrinsicWidth = indicator.getIntrinsicWidth();
|
||||
final int intrinsicHeight = indicator.getIntrinsicHeight();
|
||||
final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
|
||||
final float boundAspect = (float) w / h;
|
||||
if (intrinsicAspect != boundAspect) {
|
||||
if (boundAspect > intrinsicAspect) {
|
||||
// New width is larger. Make it smaller to match height.
|
||||
final int width = (int) (h * intrinsicAspect);
|
||||
left = (w - width) / 2;
|
||||
right = left + width;
|
||||
} else {
|
||||
// New height is larger. Make it smaller to match width.
|
||||
final int height = (int) (w * (1 / intrinsicAspect));
|
||||
top = (h - height) / 2;
|
||||
bottom = top + height;
|
||||
}
|
||||
}
|
||||
indicator.setBounds(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
drawTrack(canvas);
|
||||
}
|
||||
|
||||
void drawTrack(Canvas canvas) {
|
||||
final Drawable d = indicator;
|
||||
if (d != null) {
|
||||
// Translate canvas so a indeterminate circular progress bar with padding
|
||||
// rotates properly in its animation
|
||||
final int saveCount = canvas.save();
|
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
d.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
|
||||
if (mShouldStartAnimationDrawable && d instanceof Animatable) {
|
||||
((Animatable) d).start();
|
||||
mShouldStartAnimationDrawable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int dw = 0;
|
||||
int dh = 0;
|
||||
|
||||
final Drawable d = indicator;
|
||||
if (d != null) {
|
||||
dw = Math.max(minWidth, Math.min(maxWidth, d.getIntrinsicWidth()));
|
||||
dh = Math.max(minHeight, Math.min(maxHeight, d.getIntrinsicHeight()));
|
||||
}
|
||||
|
||||
updateDrawableState();
|
||||
|
||||
dw += getPaddingLeft() + getPaddingRight();
|
||||
dh += getPaddingTop() + getPaddingBottom();
|
||||
|
||||
final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
|
||||
final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
|
||||
setMeasuredDimension(measuredWidth, measuredHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
updateDrawableState();
|
||||
}
|
||||
|
||||
private void updateDrawableState() {
|
||||
final int[] state = getDrawableState();
|
||||
if (indicator != null && indicator.isStateful()) {
|
||||
indicator.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void drawableHotspotChanged(float x, float y) {
|
||||
super.drawableHotspotChanged(x, y);
|
||||
|
||||
if (indicator != null) {
|
||||
indicator.setHotspot(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
startAnimation();
|
||||
removeCallbacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
stopAnimation();
|
||||
// This should come after stopAnimation(), otherwise an invalidate message remains in the
|
||||
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
|
||||
super.onDetachedFromWindow();
|
||||
removeCallbacks();
|
||||
}
|
||||
|
||||
private void removeCallbacks() {
|
||||
removeCallbacks(mDelayedHide);
|
||||
removeCallbacks(mDelayedShow);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.aurora.store.view.custom.progress.indicators
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import java.util.*
|
||||
|
||||
class BallPulseIndicator : Indicator() {
|
||||
|
||||
private val scaleFloats = floatArrayOf(
|
||||
0.75f,
|
||||
1.0f,
|
||||
0.75f
|
||||
)
|
||||
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
val circleSpacing = 4f
|
||||
val radius = (width.coerceAtMost(height) - circleSpacing * 2) / 6
|
||||
val x = width / 2f - (radius * 2 + circleSpacing)
|
||||
val y = height / 2f
|
||||
|
||||
for (i in 0..2) {
|
||||
canvas.save()
|
||||
val translateX = x + radius * 2 * i + circleSpacing * i
|
||||
canvas.translate(translateX, y)
|
||||
canvas.scale(scaleFloats[i], scaleFloats[i])
|
||||
canvas.drawCircle(0f, 0f, radius, paint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAnimators(): ArrayList<ValueAnimator> {
|
||||
val animators = ArrayList<ValueAnimator>()
|
||||
val delays = intArrayOf(120, 240, 360)
|
||||
|
||||
for (i in 0..2) {
|
||||
val scaleAnim = ValueAnimator.ofFloat(1f, 0.3f, 1f)
|
||||
scaleAnim.duration = 1000
|
||||
scaleAnim.repeatCount = -1
|
||||
scaleAnim.startDelay = delays[i].toLong()
|
||||
|
||||
addUpdateListener(scaleAnim) {
|
||||
scaleFloats[i] = it.animatedValue as Float
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
animators.add(scaleAnim)
|
||||
}
|
||||
return animators
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.aurora.store.view.custom.progress.indicators;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public abstract class Indicator extends Drawable implements Animatable {
|
||||
|
||||
private static final Rect ZERO_BOUNDS_RECT = new Rect();
|
||||
|
||||
private final HashMap<ValueAnimator, ValueAnimator.AnimatorUpdateListener> updateListenerHashMap = new HashMap<>();
|
||||
private final Paint paint = new Paint();
|
||||
|
||||
private int alpha = 255;
|
||||
private ArrayList<ValueAnimator> animators;
|
||||
private boolean hasAnimators;
|
||||
|
||||
protected Rect drawBounds = ZERO_BOUNDS_RECT;
|
||||
|
||||
public Indicator() {
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return paint.getColor();
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
paint.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
draw(canvas, paint);
|
||||
}
|
||||
|
||||
public abstract void draw(Canvas canvas, Paint paint);
|
||||
|
||||
public abstract ArrayList<ValueAnimator> onCreateAnimators();
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ensureAnimators();
|
||||
|
||||
if (animators == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the animators has not ended, do nothing.
|
||||
if (isStarted()) {
|
||||
return;
|
||||
}
|
||||
startAnimators();
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private void startAnimators() {
|
||||
for (int i = 0; i < animators.size(); i++) {
|
||||
ValueAnimator animator = animators.get(i);
|
||||
|
||||
//when the animator restart , add the updateListener again because they
|
||||
// was removed by animator stop .
|
||||
ValueAnimator.AnimatorUpdateListener updateListener = updateListenerHashMap.get(animator);
|
||||
if (updateListener != null) {
|
||||
animator.addUpdateListener(updateListener);
|
||||
}
|
||||
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAnimators() {
|
||||
if (animators != null) {
|
||||
for (ValueAnimator animator : animators) {
|
||||
if (animator != null && animator.isStarted()) {
|
||||
animator.removeAllUpdateListeners();
|
||||
animator.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAnimators() {
|
||||
if (!hasAnimators) {
|
||||
animators = onCreateAnimators();
|
||||
hasAnimators = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stopAnimators();
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
for (ValueAnimator animator : animators) {
|
||||
return animator.isStarted();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
for (ValueAnimator animator : animators) {
|
||||
return animator.isRunning();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener) {
|
||||
updateListenerHashMap.put(animator, updateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
setDrawBounds(bounds);
|
||||
}
|
||||
|
||||
public void setDrawBounds(Rect drawBounds) {
|
||||
setDrawBounds(drawBounds.left, drawBounds.top, drawBounds.right, drawBounds.bottom);
|
||||
}
|
||||
|
||||
public void setDrawBounds(int left, int top, int right, int bottom) {
|
||||
this.drawBounds = new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public void postInvalidate() {
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public Rect getDrawBounds() {
|
||||
return drawBounds;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return drawBounds.width();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return drawBounds.height();
|
||||
}
|
||||
|
||||
public int centerX() {
|
||||
return drawBounds.centerX();
|
||||
}
|
||||
|
||||
public int centerY() {
|
||||
return drawBounds.centerY();
|
||||
}
|
||||
|
||||
public float exactCenterX() {
|
||||
return drawBounds.exactCenterX();
|
||||
}
|
||||
|
||||
public float exactCenterY() {
|
||||
return drawBounds.exactCenterY();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.controller
|
||||
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.store.view.epoxy.groups.CarouselModelGroup
|
||||
import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
|
||||
open class DetailsCarouselController(private val callbacks: Callbacks) :
|
||||
GenericCarouselController(callbacks) {
|
||||
|
||||
override fun buildModels(streamBundle: StreamBundle?) {
|
||||
setFilterDuplicates(true)
|
||||
if (streamBundle == null) {
|
||||
for (i in 1..2) {
|
||||
add(
|
||||
CarouselShimmerGroup()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (streamBundle.streamClusters.isNotEmpty()) {
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.controller
|
||||
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup
|
||||
import com.aurora.store.view.epoxy.groups.DeveloperModelGroup
|
||||
|
||||
open class DeveloperCarouselController(private val callbacks: Callbacks) :
|
||||
GenericCarouselController(callbacks) {
|
||||
|
||||
override fun applyFilter(streamBundle: StreamCluster): Boolean {
|
||||
return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster
|
||||
&& streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters
|
||||
}
|
||||
|
||||
override fun buildModels(streamBundle: StreamBundle?) {
|
||||
setFilterDuplicates(true)
|
||||
if (streamBundle == null) {
|
||||
for (i in 1..2) {
|
||||
add(
|
||||
CarouselShimmerGroup()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
streamBundle
|
||||
.streamClusters
|
||||
.values
|
||||
.filter { applyFilter(it) }
|
||||
.forEach { streamCluster ->
|
||||
add(DeveloperModelGroup(streamCluster, callbacks))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ 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_
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
|
||||
class EditorChoiceController(private val callbacks: Callbacks) :
|
||||
TypedEpoxyController<List<EditorChoiceBundle>>() {
|
||||
@@ -37,7 +37,7 @@ class EditorChoiceController(private val callbacks: Callbacks) :
|
||||
val idPrefix = editorChoiceBundle.id
|
||||
|
||||
add(
|
||||
EditorHeadViewModel_()
|
||||
HeaderViewModel_()
|
||||
.id("header_${idPrefix}")
|
||||
.title(editorChoiceBundle.bundleTitle)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ 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.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ 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.app.AppViewModel_
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
|
||||
class CarouselModelGroup(
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.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.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
|
||||
class DeveloperModelGroup(
|
||||
streamCluster: StreamCluster,
|
||||
callbacks: GenericCarouselController.Callbacks
|
||||
) :
|
||||
EpoxyModelGroup(
|
||||
R.layout.model_developer_carousel_group, buildModels(
|
||||
streamCluster,
|
||||
callbacks
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
private fun buildModels(
|
||||
streamCluster: StreamCluster,
|
||||
callbacks: GenericCarouselController.Callbacks
|
||||
): List<EpoxyModel<*>> {
|
||||
val models = ArrayList<EpoxyModel<*>>()
|
||||
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
val screenshotsViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
val idPrefix = streamCluster.id
|
||||
|
||||
models.add(
|
||||
HeaderViewModel_()
|
||||
.id("${idPrefix}_header")
|
||||
.title(streamCluster.clusterTitle)
|
||||
.browseUrl(streamCluster.clusterBrowseUrl)
|
||||
.click { _ ->
|
||||
callbacks.onHeaderClicked(streamCluster)
|
||||
}
|
||||
)
|
||||
|
||||
if (streamCluster.clusterAppList.size == 1) {
|
||||
val app = streamCluster.clusterAppList[0]
|
||||
|
||||
for (artwork in app.screenshots) {
|
||||
screenshotsViewModels.add(
|
||||
ScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.artwork(artwork)
|
||||
)
|
||||
}
|
||||
|
||||
clusterViewModels.add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ ->
|
||||
callbacks.onAppClick(app)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
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 (screenshotsViewModels.isNotEmpty()) {
|
||||
models.add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("${idPrefix}_screenshots")
|
||||
.models(screenshotsViewModels)
|
||||
)
|
||||
}
|
||||
|
||||
models.add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("${idPrefix}_cluster")
|
||||
.models(clusterViewModels)
|
||||
)
|
||||
|
||||
return models
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,16 @@ 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.EditorHeadViewModel_
|
||||
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
|
||||
)
|
||||
) {
|
||||
) : EpoxyModelGroup(
|
||||
R.layout.model_editorchoice_group,
|
||||
buildModels(editorChoiceCluster, callbacks)
|
||||
) {
|
||||
companion object {
|
||||
private fun buildModels(
|
||||
editorChoiceCluster: EditorChoiceCluster,
|
||||
@@ -48,21 +45,33 @@ class EditorChoiceModelGroup(
|
||||
|
||||
val idPrefix = editorChoiceCluster.id
|
||||
|
||||
|
||||
models.add(
|
||||
HeaderViewModel_()
|
||||
EditorImageViewModel_()
|
||||
.id("artwork_header_${idPrefix}")
|
||||
.artwork(editorChoiceCluster.clusterArtwork[0])
|
||||
.click { _ -> callbacks.onClick(editorChoiceCluster) }
|
||||
|
||||
)
|
||||
|
||||
models.add(
|
||||
EditorHeadViewModel_()
|
||||
.id("header_${idPrefix}")
|
||||
.title(editorChoiceCluster.clusterTitle)
|
||||
.browseUrl(editorChoiceCluster.clusterBrowseUrl)
|
||||
.click { _ -> callbacks.onClick(editorChoiceCluster) }
|
||||
)
|
||||
|
||||
editorChoiceCluster.clusterArtwork.forEach {
|
||||
clusterViewModels.add(
|
||||
EditorImageViewModel_()
|
||||
.id("artwork_${idPrefix}")
|
||||
.artwork(it)
|
||||
)
|
||||
}
|
||||
editorChoiceCluster.clusterArtwork
|
||||
.drop(1)
|
||||
.forEach {
|
||||
clusterViewModels.add(
|
||||
EditorImageViewModel_()
|
||||
.id("artwork_${idPrefix}_${it.url}")
|
||||
.artwork(it)
|
||||
.click { _ -> callbacks.onClick(editorChoiceCluster) }
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
models.add(
|
||||
CarouselHorizontalModel_()
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.aurora.store.view.epoxy.views
|
||||
import android.view.View
|
||||
import android.view.animation.AnimationUtils
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.aurora.store.view.epoxy.views.app.AppListView
|
||||
|
||||
abstract class BaseView<T : View?> : EpoxyModel<T>() {
|
||||
|
||||
|
||||
@@ -30,15 +30,15 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class BlackView : RelativeLayout {
|
||||
class BlackListView : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewBlackBinding
|
||||
|
||||
@@ -29,12 +29,12 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class CategoryView : RelativeLayout {
|
||||
|
||||
@@ -34,8 +34,8 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.google.gson.Gson
|
||||
@@ -45,7 +45,7 @@ import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class DownloadView : RelativeLayout {
|
||||
|
||||
@@ -22,13 +22,15 @@ package com.aurora.store.view.epoxy.views
|
||||
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.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewEditorHeadBinding
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class EditorHeadView : RelativeLayout {
|
||||
@@ -56,6 +58,11 @@ class EditorHeadView : RelativeLayout {
|
||||
B = ViewEditorHeadBinding.bind(view)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
B.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun title(title: String) {
|
||||
B.title.text = title
|
||||
|
||||
@@ -22,14 +22,16 @@ 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.ViewEditorImageBinding
|
||||
import com.aurora.store.util.extensions.clear
|
||||
import com.aurora.store.util.extensions.load
|
||||
import com.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.px
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
|
||||
@@ -66,23 +68,30 @@ class EditorImageView : RelativeLayout {
|
||||
fun artwork(artwork: Artwork) {
|
||||
when (artwork.type) {
|
||||
14 -> {
|
||||
B.img.layoutParams.width =
|
||||
resources.getDimension(R.dimen.icon_size_large).toInt() * 2
|
||||
B.img.layoutParams.height = 108.px.toInt()
|
||||
B.img.layoutParams.width = 192.px.toInt()
|
||||
B.img.requestLayout()
|
||||
|
||||
B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) {
|
||||
transform(RoundedCorners(16))
|
||||
transform(RoundedCorners(8.px.toInt()))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
B.img.layoutParams.width = resources.getDimension(R.dimen.icon_size_large).toInt()
|
||||
B.img.layoutParams.width = 24.px.toInt()
|
||||
B.img.layoutParams.height = 24.px.toInt()
|
||||
B.img.requestLayout()
|
||||
B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) {
|
||||
transform(RoundedCorners(16))
|
||||
transform(RoundedCorners(4.px.toInt()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
B.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@OnViewRecycled
|
||||
fun clear() {
|
||||
B.img.clear()
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.aurora.store.databinding.ViewHeaderBinding
|
||||
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class HeaderView : RelativeLayout {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.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.ViewDividerBinding
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class HorizontalDividerView : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewDividerBinding
|
||||
|
||||
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_divider, this)
|
||||
B = ViewDividerBinding.bind(view)
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import com.aurora.store.databinding.ViewActionHeaderBinding
|
||||
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class MinimalHeaderView : RelativeLayout {
|
||||
|
||||
@@ -30,8 +30,8 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
@ModelView(
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.aurora.store.databinding.ViewHeaderUpdateBinding
|
||||
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class UpdateHeaderView : RelativeLayout {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -30,9 +30,10 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
@ModelView(
|
||||
@@ -90,7 +91,7 @@ class AppListView : RelativeLayout {
|
||||
if (app.dependencies.dependentPackages.isNotEmpty())
|
||||
extras.add(getString(R.string.details_gsf_dependent))
|
||||
|
||||
B.txtLine3.text = extras.joinToString(separator = " • ")
|
||||
B.txtLine3.text = extras.joinToString(separator = " • ")
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -28,17 +28,18 @@ import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
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.aurora.store.view.epoxy.views.BaseView
|
||||
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,
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class AppUpdateView : RelativeLayout {
|
||||
@@ -76,7 +77,8 @@ class AppUpdateView : RelativeLayout {
|
||||
}
|
||||
|
||||
B.txtLine2.text = app.developerName
|
||||
B.txtLine3.text = CommonUtil.addSiPrefix(app.size)
|
||||
B.txtLine3.text = ("${CommonUtil.addSiPrefix(app.size)} • ${app.updatedOn}")
|
||||
B.txtLine4.text = ("v${app.versionName}.${app.versionCode}")
|
||||
B.txtChangelog.text = if (app.changes.isNotEmpty())
|
||||
HtmlCompat.fromHtml(
|
||||
app.changes,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -30,8 +30,9 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ 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.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -29,8 +29,8 @@ 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
|
||||
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -29,7 +29,7 @@ import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.databinding.ViewExodusBinding
|
||||
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -28,6 +28,7 @@ import com.aurora.gplayapi.data.models.File
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewFileBinding
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
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.store.R
|
||||
import com.aurora.store.databinding.ViewInfoBinding
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import java.util.*
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
)
|
||||
class InfoView : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewInfoBinding
|
||||
|
||||
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_info, this)
|
||||
B = ViewInfoBinding.bind(view)
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun badge(info: Map.Entry<String, String>) {
|
||||
B.txtTitle.text = info.key
|
||||
.replace("_", " ")
|
||||
.toLowerCase(Locale.getDefault())
|
||||
.capitalize(Locale.getDefault())
|
||||
B.txtSubtitle.text = info.value
|
||||
}
|
||||
|
||||
@OnViewRecycled
|
||||
fun clear() {
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
@@ -31,16 +31,16 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
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
|
||||
@@ -72,8 +72,10 @@ class LargeScreenshotView : RelativeLayout {
|
||||
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
val displayMetrics = Resources.getSystem().displayMetrics
|
||||
GlideApp.with(context)
|
||||
.load(artwork.url)
|
||||
.load("${artwork.url}=rw-w${displayMetrics.widthPixels}-v1-e15")
|
||||
.placeholder(R.drawable.bg_placeholder)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.addListener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
@@ -97,7 +99,6 @@ class LargeScreenshotView : RelativeLayout {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.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.Artwork
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewScreenshotMiniBinding
|
||||
import com.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
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 MiniScreenshotView : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewScreenshotMiniBinding
|
||||
|
||||
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_mini, this)
|
||||
B = ViewScreenshotMiniBinding.bind(view)
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun position(pos: Int) {
|
||||
position = pos
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
normalizeSize(artwork)
|
||||
B.img.load("${artwork.url}=rw-w480-v1-e15", 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 artworkHeight = artwork.height
|
||||
val artworkWidth = artwork.width
|
||||
|
||||
val normalizedHeight: Float
|
||||
val normalizedWidth: Float
|
||||
|
||||
when {
|
||||
artworkHeight == artworkWidth -> {
|
||||
normalizedHeight = 120f
|
||||
normalizedWidth = 120f
|
||||
}
|
||||
else -> {
|
||||
val factor = artworkHeight / 120f
|
||||
normalizedHeight = 120f
|
||||
normalizedWidth = (artworkWidth / factor)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,9 @@ 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.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseView::class
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -29,9 +29,10 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.toDate
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
package com.aurora.store.view.epoxy.views.details
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -29,13 +29,13 @@ 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.aurora.extensions.clear
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
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
|
||||
@@ -79,7 +79,7 @@ class ScreenshotView : RelativeLayout {
|
||||
@ModelProp
|
||||
fun artwork(artwork: Artwork) {
|
||||
normalizeSize(artwork)
|
||||
B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) {
|
||||
B.img.load("${artwork.url}=rw-w480-v1-e15", DrawableTransitionOptions.withCrossFade()) {
|
||||
placeholder(R.drawable.bg_rounded)
|
||||
transform(RoundedCorners(8.px.toInt()))
|
||||
}
|
||||
@@ -88,22 +88,22 @@ class ScreenshotView : RelativeLayout {
|
||||
private fun normalizeSize(artwork: Artwork) {
|
||||
if (artwork.height != 0 && artwork.width != 0) {
|
||||
|
||||
val viewHeight = artwork.height
|
||||
val viewWidth = artwork.width
|
||||
val artworkHeight = artwork.height
|
||||
val artworkWidth = artwork.width
|
||||
|
||||
var normalizedHeight: Int = viewHeight
|
||||
var normalizedWidth: Int = viewWidth
|
||||
val normalizedHeight: Float
|
||||
val normalizedWidth: Float
|
||||
|
||||
|
||||
if (viewHeight == viewWidth) {
|
||||
normalizedHeight = 240
|
||||
normalizedWidth = 240
|
||||
} else if (viewHeight > viewWidth) {
|
||||
normalizedHeight = 240
|
||||
normalizedWidth = 135
|
||||
} else if (viewHeight < viewWidth) {
|
||||
normalizedHeight = 240
|
||||
normalizedWidth = 427
|
||||
when {
|
||||
artworkHeight == artworkWidth -> {
|
||||
normalizedHeight = 192f
|
||||
normalizedWidth = 192f
|
||||
}
|
||||
else -> {
|
||||
val factor = artworkHeight / 192f
|
||||
normalizedHeight = 192f
|
||||
normalizedWidth = (artworkWidth / factor)
|
||||
}
|
||||
}
|
||||
|
||||
B.img.layoutParams.height = normalizedHeight.px.toInt()
|
||||
|
||||
@@ -76,4 +76,9 @@ class InstallerView : RelativeLayout {
|
||||
fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) {
|
||||
B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
B.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,9 @@ 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.hide
|
||||
import com.aurora.store.util.extensions.load
|
||||
import com.aurora.store.util.extensions.show
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
|
||||
@ModelView(
|
||||
|
||||
@@ -26,10 +26,10 @@ 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.extensions.browse
|
||||
import com.aurora.extensions.close
|
||||
import com.aurora.extensions.copyToClipBoard
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.store.view.epoxy.views.preference.LinkViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
|
||||
|
||||
@@ -19,22 +19,19 @@
|
||||
|
||||
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.extensions.browse
|
||||
import com.aurora.extensions.close
|
||||
import com.aurora.extensions.load
|
||||
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
|
||||
@@ -224,14 +221,6 @@ class AccountActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ 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.util.AC2DMUtil
|
||||
import com.aurora.extensions.close
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
import nl.komponents.kovenant.task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@@ -79,7 +79,7 @@ class GoogleActivity : BaseActivity() {
|
||||
B.webview.webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
val cookies = CookieManager.getInstance().getCookie(url)
|
||||
val cookieMap = Util.parseCookieString(cookies)
|
||||
val cookieMap = AC2DMUtil.parseCookieString(cookies)
|
||||
if (cookieMap.isNotEmpty() && cookieMap[AUTH_TOKEN] != null) {
|
||||
val oauthToken = cookieMap[AUTH_TOKEN]
|
||||
B.webview.evaluateJavascript("(function() { return document.getElementById('profileIdentifier').innerHTML; })();") {
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.extensions.close
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
@@ -73,6 +73,7 @@ class AppsGamesActivity : BaseActivity() {
|
||||
when (position) {
|
||||
0 -> tab.text = getString(R.string.title_installed)
|
||||
1 -> tab.text = getString(R.string.title_library)
|
||||
2 -> tab.text = getString(R.string.title_purchase_history)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
@@ -89,6 +90,7 @@ class AppsGamesActivity : BaseActivity() {
|
||||
return when (position) {
|
||||
0 -> InstalledAppsFragment.newInstance()
|
||||
1 -> LibraryAppsFragment.newInstance()
|
||||
2 -> PurchasedAppsFragment.newInstance()
|
||||
else -> Fragment()
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ class AppsGamesActivity : BaseActivity() {
|
||||
return if (isAnonymous)
|
||||
1
|
||||
else
|
||||
2
|
||||
3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ 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.app.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
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.app.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_
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.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.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.app.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
|
||||
import com.aurora.store.viewmodel.all.PurchasedViewModel
|
||||
|
||||
class PurchasedAppsFragment : BaseFragment() {
|
||||
|
||||
private lateinit var VM: PurchasedViewModel
|
||||
private lateinit var B: FragmentUpdatesBinding
|
||||
lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(): PurchasedAppsFragment {
|
||||
return PurchasedAppsFragment().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(PurchasedViewModel::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()
|
||||
}
|
||||
|
||||
attachRecycler()
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun attachRecycler() {
|
||||
endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
VM.observe()
|
||||
}
|
||||
}
|
||||
B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
}
|
||||
|
||||
|
||||
private fun updateController(appList: List<App>?) {
|
||||
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 purchased")
|
||||
)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -70,10 +70,10 @@ class AppsContainerFragment : Fragment() {
|
||||
|
||||
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"
|
||||
0 -> tab.text = getString(R.string.tab_for_you)
|
||||
1 -> tab.text = getString(R.string.tab_top_charts)
|
||||
2 -> tab.text = getString(R.string.tab_categories)
|
||||
3 -> tab.text = getString(R.string.tab_editor_choice)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ 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.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.topchart.*
|
||||
@@ -40,6 +40,8 @@ class TopChartFragment : BaseFragment() {
|
||||
private lateinit var VM: BaseChartViewModel
|
||||
private lateinit var B: FragmentTopContainerBinding
|
||||
|
||||
private lateinit var streamCluster: StreamCluster
|
||||
|
||||
private var chartType = 0
|
||||
private var chartCategory = 0
|
||||
|
||||
@@ -88,16 +90,23 @@ class TopChartFragment : BaseFragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
VM.liveData.observe(viewLifecycleOwner, {
|
||||
updateController(it)
|
||||
if (!::streamCluster.isInitialized)
|
||||
attachRecycler()
|
||||
|
||||
streamCluster = it
|
||||
|
||||
updateController(streamCluster)
|
||||
})
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun attachRecycler() {
|
||||
B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
VM.nextCluster()
|
||||
}
|
||||
})
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(streamCluster: StreamCluster?) {
|
||||
|
||||
@@ -24,19 +24,16 @@ 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.extensions.applyTheme
|
||||
import com.aurora.extensions.getEmptyActivityBundle
|
||||
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.details.*
|
||||
import com.aurora.store.view.ui.sheets.NetworkDialogSheet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
@@ -99,42 +96,66 @@ abstract class BaseActivity : AppCompatActivity(), NetworkProvider.NetworkListen
|
||||
}
|
||||
}
|
||||
|
||||
fun openStreamBrowseActivity(browseUrl: String) {
|
||||
val intent = Intent(this, StreamBrowseActivity::class.java)
|
||||
fun openStreamBrowseActivity(browseUrl: String, title: String = "") {
|
||||
val intent = if (browseUrl.toLowerCase().contains("expanded"))
|
||||
Intent(this, ExpandedStreamBrowseActivity::class.java)
|
||||
else if (browseUrl.toLowerCase().contains("developer"))
|
||||
Intent(this, DevProfileActivity::class.java)
|
||||
else
|
||||
Intent(this, StreamBrowseActivity::class.java)
|
||||
intent.putExtra(Constants.BROWSE_EXTRA, browseUrl)
|
||||
intent.putExtra(Constants.STRING_EXTRA, title)
|
||||
startActivity(
|
||||
intent,
|
||||
ViewUtil.getEmptyActivityBundle(this)
|
||||
getEmptyActivityBundle()
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun openGoogleActivity() {
|
||||
val intent = Intent(this, GoogleActivity::class.java)
|
||||
startActivity(
|
||||
intent,
|
||||
ViewUtil.getEmptyActivityBundle(this)
|
||||
getEmptyActivityBundle()
|
||||
)
|
||||
}
|
||||
|
||||
fun showNetworkConnectivitySheet() {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(NetworkDialogSheet.newInstance(0), "NDS")
|
||||
.commitAllowingStateLoss()
|
||||
runOnUiThread {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(NetworkDialogSheet.newInstance(), NetworkDialogSheet.TAG)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
fun hideNetworkConnectivitySheet() {
|
||||
val fragment: Fragment? = supportFragmentManager.findFragmentByTag("NDS")
|
||||
if (fragment != null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commitAllowingStateLoss()
|
||||
runOnUiThread {
|
||||
val fragment = supportFragmentManager.findFragmentByTag(NetworkDialogSheet.TAG)
|
||||
fragment?.let {
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
NetworkProvider.addListener(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
||||
@@ -24,10 +24,11 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.getEmptyActivityBundle
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Category
|
||||
import com.aurora.store.util.ViewUtil
|
||||
import com.aurora.store.view.ui.details.AppDetailsActivity
|
||||
import com.aurora.store.view.ui.details.DevProfileActivity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.lang.reflect.Modifier
|
||||
@@ -63,12 +64,23 @@ open class BaseFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fun openStreamBrowseActivity(browseUrl: String) {
|
||||
val intent = Intent(requireContext(), StreamBrowseActivity::class.java)
|
||||
fun openStreamBrowseActivity(browseUrl: String, title: String = "") {
|
||||
val intent = if (browseUrl.toLowerCase().contains("expanded"))
|
||||
Intent(requireContext(), ExpandedStreamBrowseActivity::class.java)
|
||||
else if (browseUrl.toLowerCase().contains("developer"))
|
||||
Intent(requireContext(), DevProfileActivity::class.java)
|
||||
else
|
||||
Intent(requireContext(), StreamBrowseActivity::class.java)
|
||||
|
||||
intent.putExtra(Constants.BROWSE_EXTRA, browseUrl)
|
||||
startActivity(
|
||||
intent,
|
||||
ViewUtil.getEmptyActivityBundle(requireContext())
|
||||
)
|
||||
intent.putExtra(Constants.STRING_EXTRA, title)
|
||||
startActivity(intent, requireContext().getEmptyActivityBundle())
|
||||
}
|
||||
|
||||
fun openEditorStreamBrowseActivity(browseUrl: String, title: String = "") {
|
||||
val intent = Intent(requireContext(), EditorStreamBrowseActivity::class.java)
|
||||
intent.putExtra(Constants.BROWSE_EXTRA, browseUrl)
|
||||
intent.putExtra(Constants.STRING_EXTRA, title)
|
||||
startActivity(intent, requireContext().getEmptyActivityBundle())
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ 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.extensions.close
|
||||
import com.aurora.store.view.epoxy.views.BlackListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.all.BlacklistViewModel
|
||||
|
||||
@@ -94,7 +94,7 @@ class BlacklistActivity : BaseActivity() {
|
||||
} else {
|
||||
blackList.forEach {
|
||||
add(
|
||||
BlackViewModel_()
|
||||
BlackListViewModel_()
|
||||
.id(it.packageName.hashCode())
|
||||
.black(it)
|
||||
.markChecked(VM.selected.contains(it.packageName))
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.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
|
||||
|
||||
@@ -123,8 +123,6 @@ class EarlyAccessFragment : BaseFragment(), GenericCarouselController.Callbacks
|
||||
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) {
|
||||
|
||||
@@ -23,7 +23,6 @@ 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
|
||||
@@ -35,7 +34,7 @@ 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 {
|
||||
class EditorChoiceFragment : BaseFragment(), EditorChoiceController.Callbacks {
|
||||
|
||||
private lateinit var B: FragmentForYouBinding
|
||||
private lateinit var C: EditorChoiceController
|
||||
@@ -97,6 +96,9 @@ class EditorChoiceFragment : Fragment(), EditorChoiceController.Callbacks {
|
||||
}
|
||||
|
||||
override fun onClick(editorChoiceCluster: EditorChoiceCluster) {
|
||||
|
||||
openEditorStreamBrowseActivity(
|
||||
editorChoiceCluster.clusterBrowseUrl,
|
||||
editorChoiceCluster.clusterTitle
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.databinding.ActivityGenericRecyclerBinding
|
||||
import com.aurora.extensions.close
|
||||
import com.aurora.store.view.epoxy.groups.CarouselHorizontalModel_
|
||||
import com.aurora.store.view.epoxy.views.EditorHeadViewModel_
|
||||
import com.aurora.store.view.epoxy.views.HorizontalDividerViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.editorschoice.EditorBrowseViewModel
|
||||
|
||||
|
||||
class EditorStreamBrowseActivity : BaseActivity() {
|
||||
|
||||
lateinit var B: ActivityGenericRecyclerBinding
|
||||
lateinit var VM: EditorBrowseViewModel
|
||||
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(EditorBrowseViewModel::class.java)
|
||||
|
||||
setContentView(B.root)
|
||||
|
||||
attachToolbar()
|
||||
|
||||
VM.liveData.observe(this, {
|
||||
updateController(it)
|
||||
})
|
||||
|
||||
intent.apply {
|
||||
getStringExtra(Constants.BROWSE_EXTRA)?.let {
|
||||
VM.getEditorStreamBundle(it)
|
||||
}
|
||||
getStringExtra(Constants.STRING_EXTRA)?.let {
|
||||
B.layoutToolbarAction.txtTitle.text = it
|
||||
}
|
||||
}
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun attachToolbar() {
|
||||
B.layoutToolbarAction.imgActionPrimary.setOnClickListener {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateController(appList: MutableList<App>?) {
|
||||
B.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (appList == null) {
|
||||
for (i in 1..6) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
appList.forEach { app ->
|
||||
val screenshotsViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
for ((position, artwork) in app.screenshots.withIndex()) {
|
||||
screenshotsViewModels.add(
|
||||
MiniScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.position(position)
|
||||
.artwork(artwork)
|
||||
.callback(object : MiniScreenshotView.ScreenshotCallback {
|
||||
override fun onClick(position: Int) {
|
||||
openScreenshotActivity(app, position)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id("app_${app.id}")
|
||||
.app(app)
|
||||
.click { _ -> openDetailsActivity(app) }
|
||||
)
|
||||
|
||||
app.editorReason?.let { editorReason ->
|
||||
add(
|
||||
EditorHeadViewModel_()
|
||||
.id("bulletin_${app.id}")
|
||||
.title(
|
||||
editorReason.bulletins
|
||||
.joinToString(transform = { "\n• $it" })
|
||||
.substringAfter(delimiter = "\n")
|
||||
)
|
||||
.click { _ -> openDetailsActivity(app) }
|
||||
)
|
||||
}
|
||||
|
||||
if (screenshotsViewModels.isNotEmpty()) {
|
||||
add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("screenshots_${app.id}")
|
||||
.models(screenshotsViewModels)
|
||||
)
|
||||
}
|
||||
|
||||
app.editorReason?.let { editorReason ->
|
||||
if (editorReason.description.isNotEmpty()) {
|
||||
add(
|
||||
EditorHeadViewModel_()
|
||||
.id("description_${app.id}")
|
||||
.title(editorReason.description)
|
||||
.click { _ -> openDetailsActivity(app) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
add(
|
||||
HorizontalDividerViewModel_()
|
||||
.id("divider_${app.id}")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user