Compare commits

..

No commits in common. "dev" and "v2.3.0" have entirely different histories.
dev ... v2.3.0

241 changed files with 4354 additions and 6390 deletions

View File

@ -0,0 +1,29 @@
---
name: Bug issue template
about: Vanced Manager Bug template
title: ''
labels: ''
assignees: ''
---
**Please only report your issue here, if all points below are true**
- I installed the App from [vancedapp.com](https://vancedapp.com), this github repository or the Vanced Discord server
- I am using the latest version
- This is an issue in the Vanced Manager app (NOT Youtube Vanced)
- This issue keeps re-occurring every time I try
- For MIUI users: I disabled MIUI optimisation
**Phone Specifications:**
- Brand:
- Operating System:
- Android Version:
- Vanced Manager Version:
**Please describe the problem you are having in as much detail as possible:**
**Steps to reproduce:**
**Further details:**

View File

@ -1,41 +0,0 @@
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug"]
assignees:
- X1nto
body:
- type: textarea
id: device-info
attributes:
label: Device
description: What device are you using?
value: |
Device:
Operating System:
Android Version:
validations:
required: true
- type: textarea
id: manager-version
attributes:
label: Version
description: What version of Vanced Manager are you using?
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Bug Description
description: Describe the bug and how to reproduce it in as much detail as possible.
validations:
required: true
- type: checkboxes
id: is-manager-bug
attributes:
label: Additional checks
options:
- label: I have checked other bug reports and this is not a duplicate.
required: true
- label: This is a bug in Vanced Manager and NOT YouTube Vanced/YouTube Vanced Music/Vanced microG.
required: true

View File

@ -1 +0,0 @@
blank_issues_enabled: false

View File

@ -1,29 +0,0 @@
name: Feature Request
description: Request a feature
title: "[Feature]: "
labels: ["enhancement"]
assignees:
- X1nto
body:
- type: textarea
id: suggestion
attributes:
label: Suggestion
validations:
required: true
- type: textarea
id: suggestion-relevancy
attributes:
label: Additional Information
description: Why is this suggestion relevant?
validations:
required: true
- type: checkboxes
id: is-manager-suggestion
attributes:
label: Additional checks
options:
- label: I have checked other feature requests and this is not a duplicate.
required: true
- label: This is a suggestion for Vanced Manager and NOT YouTube Vanced/YouTube Vanced Music/Vanced microG.
required: true

View File

@ -4,28 +4,27 @@ on:
push:
branches:
- dev
paths-ignore:
- '**.md'
pull_request:
branches:
- dev
paths-ignore:
- '**.md'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 11
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 1.8
- name: Grant rights
run: chmod +x ./gradlew
- name: Build debug APK with Gradle
- name: Build project with Gradle
run: ./gradlew build
- name: Build Debug APK with Gradle
run: ./gradlew assembleDebug
- name: Upload Debug

2
.gitignore vendored
View File

@ -6,5 +6,5 @@ app/src/main/java/com/vanced/manager/core/base/DummyJava.java
app/build/
app/release
local.properties
/.github/
*.iml
.vscode/

View File

@ -1,64 +1,35 @@
Pull requests should be made to the Dev branch as that is the working branch, master is for release code.
======
For anyone who wants to provide translations please submit them to https://crowdin.com/project/vanced-manager as we also use it for YouTube Vanced. Any issues with translations should be posted there too.
======
Vanced FAQ (from the faq branch) now available on the playstore! https://play.google.com/store/apps/details?id=com.vanced.faq
## The FAQ app has just been suspended due to "Impersonating Vanced", an appeal has been filed, reporting other apps which impersonate Vanced on the play store is appreciated.
[![Github All Releases](https://img.shields.io/github/downloads/YTVanced/VancedManager/total.svg)](https://github.com/YTVanced/VancedManager/releases/latest) [![Github All Releases](https://img.shields.io/github/release/YTVanced/VancedManager.svg)](https://github.com/YTVanced/VancedManager/releases/latest)
# Vanced Manager
<div>
[![Github All Releases](https://img.shields.io/github/downloads/YTVanced/VancedManager/total.svg?style=for-the-badge)](https://github.com/YTVanced/VancedManager/releases/latest) [![Github All Releases](https://img.shields.io/github/release/YTVanced/VancedManager.svg?style=for-the-badge)](https://github.com/YTVanced/VancedManager/releases/latest)
</div>
## Introduction
Hi, when we released Vanced 15.05.54, people were upset because it used the .apks format, which was burdensome to install than a traditional .apk file. Even though we wrote clear instructions on how to install the new Vanced build, people still couldn't figure it out.
Then we thought, "why don't we make a manager for vanced, which will download, update and uninstall Vanced and MicroG, have an easy and user-friendly UI and be less than 10mb?" and that's how Vanced Manager was born.
Hi, when we released Vanced 15.05.54, people were upset because it used the .apks format, which was way harder to install than a traditional .apk file. Even though we wrote clear instructions on how to install the new Vanced build, people still couldn't figure it out.
Then we thought, "why don't we make a manager for vanced, which will download, update and uninstall Vanced and MicroG, have an easy and understandable UI and be less than 10mb?" and that's how Vanced Manager was born.
After 3 months of development, we are finally ready to Introduce [Vanced Manager](https://github.com/YTVanced/VancedManager) to the masses!!
After 3 months of development, we are finally ready to introduce Vanced Manager to the masses. Vanced manager can easily install and uninstall vanced and microg, has various settings for customisation and better experience. The Manager comes with an easy-to-use interface
## Features
##### Background download/installation feature is no longer supported due to problems with some ROMs, please do NOT report issues regarding background activity.
- Vanced manager can easily install and uninstall Vanced and MicroG.
- It has various settings for customization and better experience.
- The Manager comes with an easy-to-use Interface.
## Vanced Developers
- xfileFIN
- KevinX8
- Zanezam
- Laura Almeida
</br>
## Vanced Manager Developers
- Xinto (X1nto)
- Koopah (ostajic)
<div class="note">
<p><strong>NOTE: </strong>Background download/installation feature is no longer supported due to problems with some ROMs, please <b>DO NOT</b> report issues regarding background activity.</p>
</div>
<!-- ##### Background download/installation feature is no longer supported due to problems with some ROMs, please do NOT report issues regarding background activity. -->
## Contributions
Pull Requests should be made to the [Dev](https://github.com/YTVanced/VancedManager) Branch as that is the working branch, master is for Release code only.
For anyone who wants to provide translations please submit them to this [link](https://crowdin.com/project/vanced-manager) as we also use it for YouTube Vanced. Any issues with translations should be posted there too.
## TODO
- [ ] Clean up the ViewModel and DataModel code
- [ ] Migrate to Jetpack Compose when it's officially released
## Building
<div>
[![Build](https://github.com/YTVanced/VancedManager/actions/workflows/debug.yml/badge.svg?branch=dev)](https://github.com/YTVanced/VancedManager/actions/workflows/debug.yml)
</div>
## Using Android Studio
Clone the Repository, open it in Android Studio and build the application.
## Google Advanced Protection Program
If you are using this feature on your Google account, you must either disable it or log out from your Google account before installing Youtube Vanced via Vanced Manager.
The Google Advanced Protection Program does not allow the installation of apps from unknown sources. These security measures are tied to the protected account and not the device. After the installation, you will be able to log back in or enroll again into the program.
## Using Command Line
#### On Windows:
```powershell
.\gradlew.bat assembleDebug
```
#### On Linux/macOS:
```bash
chmod +x gradlew
./gradlew assembleDebug
```
## Contributors
- AioiLight
- HaliksaR
## Credits
- topjohnwu for his wonderful [LibSU](https://github.com/topjohnwu/libsu)
- aefyr for [SAI](https://github.com/aefyr/SAI), which was an inspiration for our Manager
- kittinunf for [Fuel](https://github.com/kittinunf/Fuel) HTTP client
- cbeust for [klaxon](https://github.com/cbeust/klaxon) JSON parser

View File

@ -48,3 +48,6 @@ Vanced Manager sucks 100% of your CPU to mine Bitcoins, this is a new technique
![Zanezam](https://i.imgur.com/QVcXA6q.png)
- Laura Almeida
![Laura Almeida](https://i.imgur.com/ovVD939.png)
###### If someone is reading this pls help me, KevinX8 is bullying me and forces me to develop manager. please send bobs and veganas and call 911

View File

@ -6,27 +6,28 @@ plugins {
id("com.google.firebase.crashlytics")
id("com.google.firebase.firebase-perf")
id("androidx.navigation.safeargs.kotlin")
id("kotlin-android")
}
android {
compileSdk = 31
compileSdkVersion(30)
defaultConfig {
applicationId = "com.vanced.manager"
minSdk = 21
targetSdk = 31
versionCode = 262
versionName = "2.6.2 (Crimson)"
minSdkVersion(21)
targetSdkVersion(30)
versionCode = 230
versionName = "2.3.0 (MicroShitMoment)"
vectorDrawables {
useSupportLibrary = true
}
vectorDrawables.useSupportLibrary = true
buildConfigField("String[]", "MANAGER_LANGUAGES", "{$languages}")
buildConfigField("String[]", "MANAGER_LANGUAGES", "{" + getLanguages() + "}")
buildConfigField("Boolean", "ENABLE_CROWDIN_AUTH", "false")
buildConfigField("String", "CROWDIN_HASH", "\"${System.getenv("CROWDIN_HASH")}\"")
buildConfigField("String", "CROWDIN_CLIENT_ID", "\"${System.getenv("CROWDIN_CLIENT_ID")}\"")
buildConfigField("String", "CROWDIN_CLIENT_SECRET", "\"${System.getenv("CROWDIN_CLIENT_SECRET")}\"")
}
lint {
lintOptions {
disable("MissingTranslation", "ExtraTranslation")
}
@ -42,14 +43,13 @@ android {
}
buildFeatures {
dataBinding = true // ObservableField migrate to flow or liveData
viewBinding = true
}
packagingOptions {
resources {
excludes += "META-INF/DEPENDENCIES"
excludes += "META-INF/*.kotlin_module"
}
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/*.kotlin_module")
}
// To inline the bytecode built with JVM target 1.8 into
@ -68,72 +68,75 @@ android {
}
val languages: String get() {
fun getLanguages(): String {
val langs = arrayListOf("en", "bn_BD", "bn_IN", "pa_IN", "pa_PK", "pt_BR", "pt_PT", "zh_CN", "zh_TW")
val exceptions = arrayOf("bn", "pa", "pt", "zh")
File("$projectDir/src/main/res").listFiles()?.filter {
val name = it.name
name.startsWith("values-") && !name.contains("v23")
}?.forEach { dir ->
val dirname = dir.name.substringAfter("-").substringBefore("-")
if (!exceptions.contains(dirname)) {
langs.add(dirname)
File("$projectDir/src/main/res").listFiles()?.forEach { dir ->
if (dir.name.startsWith("values-") && !dir.name.contains("v23")) {
val dirname = dir.name.substringAfter("-").substringBefore("-")
if (!exceptions.any { dirname == it }) {
langs.add(dirname)
}
}
}
return langs.joinToString(", ") { "\"$it\"" }
}
dependencies {
implementation(project(":core-presentation"))
implementation(project(":core-ui"))
implementation(project(":library-network"))
// Kotlin
// Kotlin
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
// AndroidX
implementation("androidx.appcompat:appcompat:1.3.1")
// AndroidX
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.1")
implementation("androidx.core:core-ktx:1.6.0")
implementation("androidx.fragment:fragment-ktx:1.3.6")
implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.2.5")
implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
implementation("androidx.navigation:navigation-fragment-ktx:2.3.2")
implementation("androidx.navigation:navigation-ui-ktx:2.3.2")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.7.0-rc01")
implementation("com.github.madrapps:pikolo:2.0.2")
implementation("com.google.android.material:material:1.5.0-alpha04")
//Appearance
implementation("com.github.madrapps:pikolo:2.0.1")
implementation("com.google.android.material:material:1.3.0-rc01")
// JSON parser
implementation("com.beust:klaxon:5.5")
implementation("com.beust:klaxon:5.4")
// Crowdin
implementation("com.crowdin.platform:mobile-sdk:1.2.0")
// Tips
implementation("com.github.florent37:viewtooltip:1.2.2")
// HTTP networking
implementation("com.github.kittinunf.fuel:fuel:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-json:2.3.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.2")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.github.kittinunf.fuel:fuel:2.3.0")
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.2.3")
implementation("com.github.kittinunf.fuel:fuel-json:2.2.3")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
// Root permissions
val libsuVersion = "3.1.2"
implementation("com.github.topjohnwu.libsu:core:$libsuVersion")
implementation("com.github.topjohnwu.libsu:io:$libsuVersion")
//implementation("com.github.topjohnwu.libsu:busybox:$libsuVersion")
implementation("com.github.topjohnwu.libsu:core:3.0.2")
implementation("com.github.topjohnwu.libsu:io:3.0.2")
// Layout
implementation("com.google.android.flexbox:flexbox:3.0.0")
implementation("com.google.android:flexbox:2.0.1")
// Firebase
implementation("com.google.firebase:firebase-analytics-ktx:19.0.2")
implementation("com.google.firebase:firebase-crashlytics:18.2.3")
implementation("com.google.firebase:firebase-messaging:22.0.0")
implementation("com.google.firebase:firebase-perf:20.0.3")
implementation("com.google.firebase:firebase-analytics-ktx:18.0.1")
implementation("com.google.firebase:firebase-crashlytics:17.3.0")
implementation("com.google.firebase:firebase-messaging:21.0.1")
implementation("com.google.firebase:firebase-perf:19.1.0")
}

View File

@ -17,7 +17,7 @@
# Uncomment this to preserve the line number information for
# debugging stack traces.
-keepattributes SourceFile, LineNumberTable
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.

View File

@ -6,9 +6,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- is required for some Android 5.x devices -->
<uses-permission
@ -21,7 +21,6 @@
<package android:name="com.vanced.android.apps.youtube.music" />
<package android:name="com.google.android.apps.youtube.music" />
<package android:name="com.mgoogle.android.gms" />
<package android:name="com.vanced.faq" />
<package android:name="com.android.vending" />
</queries>
@ -32,13 +31,13 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
android:supportsRtl="true"
tools:ignore="UnusedAttribute">
<activity
android:name=".ui.SplashScreenActivity"
android:name=".ui.core.SplashScreenActivity"
android:label="@string/app_name"
android:theme="@style/SplashTheme"
android:exported="true">
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -56,10 +55,10 @@
<activity
android:name=".ui.MainActivity"
android:configChanges="layoutDirection|locale|keyboardHidden|orientation|screenSize"
android:configChanges="layoutDirection|locale"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/DarkTheme"
android:exported="true">
android:theme="@style/DarkTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -67,7 +66,8 @@
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="api.vancedapp.com"/>
android:host="vancedapp.com"
android:pathPrefix="/downloads"/>
</intent-filter>
@ -76,8 +76,8 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:grantUriPermissions="true"
android:exported="false">
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,123 @@
package com.vanced.manager.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.RecyclerView
import com.github.florent37.viewtooltip.ViewTooltip
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewAppBinding
import com.vanced.manager.model.DataModel
import com.vanced.manager.model.RootDataModel
import com.vanced.manager.ui.dialogs.AppInfoDialog
import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.utils.enableMusic
import com.vanced.manager.utils.enableVanced
import com.vanced.manager.utils.managerVariant
class AppListAdapter(
private val context: FragmentActivity,
private val viewModel: HomeViewModel,
private val lifecycleOwner: LifecycleOwner,
private val tooltip: ViewTooltip
) : RecyclerView.Adapter<AppListAdapter.ListViewHolder>() {
val apps = mutableListOf<String>()
private val dataModels = mutableListOf<DataModel?>()
private val rootDataModels = mutableListOf<RootDataModel?>()
private val prefs = getDefaultSharedPreferences(context)
private var itemCount = 0
private val isRoot = prefs.managerVariant == "root"
inner class ListViewHolder(private val binding: ViewAppBinding) : RecyclerView.ViewHolder(binding.root) {
val appCard = binding.appCard
fun bind(position: Int) {
val dataModel = if (isRoot) rootDataModels[position] else dataModels[position]
with(binding) {
appName.text = dataModel?.appName
dataModel?.buttonTxt?.observe(lifecycleOwner) {
appInstallButton.text = it
}
appInstallButton.setOnClickListener {
viewModel.openInstallDialog(it, apps[position])
}
appUninstall.setOnClickListener {
dataModel?.appPkg?.let { it1 -> viewModel.uninstallPackage(it1) }
}
appLaunch.setOnClickListener {
viewModel.launchApp(apps[position], isRoot)
}
with(dataModel?.isAppInstalled?.value) {
appUninstall.isVisible = this == true
appLaunch.isVisible = this == true
}
dataModel?.isAppInstalled?.observe(lifecycleOwner) {
appUninstall.isVisible = it
appLaunch.isVisible = it
}
dataModel?.versionName?.observe(lifecycleOwner) {
appRemoteVersion.text = it
}
dataModel?.installedVersionName?.observe(lifecycleOwner) {
appInstalledVersion.text = it
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val view = ViewAppBinding.inflate(LayoutInflater.from(context), parent, false)
return ListViewHolder(view)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(position)
val dataModel = if (isRoot) rootDataModels[position] else dataModels[position]
holder.appCard.setOnClickListener {
tooltip.close()
AppInfoDialog.newInstance(
appName = apps[position],
appIcon = dataModel?.appIcon,
changelog = dataModel?.changelog?.value
).show(context.supportFragmentManager, "info")
}
}
override fun getItemCount(): Int = itemCount
init {
if (prefs.enableVanced) {
if (isRoot) {
rootDataModels.add(viewModel.vancedRootModel.value)
} else {
dataModels.add(viewModel.vancedModel.value)
}
apps.add(context.getString(R.string.vanced))
itemCount++
}
if (prefs.enableMusic) {
if (isRoot) {
rootDataModels.add(viewModel.musicRootModel.value)
} else {
dataModels.add(viewModel.musicModel.value)
}
apps.add(context.getString(R.string.music))
itemCount++
}
if (!isRoot) {
dataModels.add(viewModel.microgModel.value)
apps.add(context.getString(R.string.microg))
itemCount++
}
}
}

View File

@ -1,199 +0,0 @@
package com.vanced.manager.adapter
import android.animation.ValueAnimator
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.animation.addListener
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewAppExpandableBinding
import com.vanced.manager.model.ButtonTag
import com.vanced.manager.model.DataModel
import com.vanced.manager.ui.dialogs.AppInfoDialog
import com.vanced.manager.ui.dialogs.AppUninstallDialog
import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.utils.*
class ExpandableAppListAdapter(
private val activity: FragmentActivity,
private val viewModel: HomeViewModel
) : RecyclerView.Adapter<ExpandableAppListAdapter.ListViewHolder>() {
private val apps = mutableListOf<String>()
private val dataModels = mutableListOf<DataModel?>()
private val prefs = getDefaultSharedPreferences(activity)
private val isRoot = prefs.managerVariant == "root"
private var isAnimationRunning = false
inner class ListViewHolder(private val binding: ViewAppExpandableBinding) :
RecyclerView.ViewHolder(binding.root) {
private var isExpanded = false
fun bind(position: Int) {
val dataModel = dataModels[position]
with(binding) {
appTitle.text = dataModel?.appName
appDescription.text = dataModel?.appDescription
dataModel?.appIcon?.let { appIcon.setImageResource(it) }
appClickableLayout.setOnClickListener {
if (isAnimationRunning) return@setOnClickListener
val rootHeight = root.measuredHeight
val expandedViewHeight = appExpandedView.height
val expandedTranslation = appClickableLayout.height.toFloat()
when (isExpanded.also { isExpanded = !isExpanded }) {
true -> {
appExpandedView.toggle(0f, 0.8f, -expandedTranslation)
root.toggleCard(rootHeight - expandedViewHeight)
appExpandArrow.rotateArrow(90f)
}
false -> {
root.toggleCard(rootHeight + expandedViewHeight)
appExpandedView.toggle(1f, 1f, expandedTranslation)
appExpandArrow.rotateArrow(-90f)
}
}
}
appUninstall.setOnClickListener {
AppUninstallDialog.newInstance(
dataModel?.appName,
dataModel?.appPkg
).show(activity.supportFragmentManager, null)
}
appLaunch.setOnClickListener {
viewModel.launchApp(apps[position], isRoot)
}
appInfo.setOnClickListener {
AppInfoDialog.newInstance(
appName = apps[position],
appIcon = dataModel?.appIcon,
changelog = dataModel?.changelog?.value
).show(activity.supportFragmentManager, "info")
}
dataModel?.buttonTag?.observe(activity) { buttonTag ->
appDownload.apply {
setOnClickListener {
viewModel.openInstallDialog(
activity.supportFragmentManager,
buttonTag,
apps[position]
)
}
appDownload.setIconResource(buttonTag.image)
contentDescription = activity.getString(
when (buttonTag) {
ButtonTag.UPDATE -> R.string.accessibility_update
ButtonTag.REINSTALL -> R.string.accessibility_reinstall
else -> R.string.accessibility_download
}
)
}
}
dataModel?.isAppInstalled?.observe(activity) {
appUninstall.isVisible = it
appLaunch.isVisible = it
}
dataModel?.versionName?.observe(activity) {
appVersionLatest.text = it
appDownload.isGone = it == activity.getString(R.string.unavailable)
}
dataModel?.installedVersionName?.observe(activity) {
appVersionInstalled.text = it
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val view = ViewAppExpandableBinding.inflate(LayoutInflater.from(activity), parent, false)
return ListViewHolder(view)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int = apps.size
private fun ImageView.rotateArrow(degrees: Float) {
animate().apply {
duration = animationDuration
rotation(degrees)
}
}
private fun View.toggle(
alpha: Float,
scale: Float,
translation: Float
) {
animate().apply {
duration = animationDuration
scaleX(scale)
scaleY(scale)
alpha(alpha)
translationYBy(translation)
}
}
private fun MaterialCardView.toggleCard(resultHeight: Int) {
ValueAnimator.ofInt(measuredHeight, resultHeight).apply {
duration = animationDuration
addUpdateListener { value ->
updateLayoutParams {
height = value.animatedValue as Int
}
}
addListener(
onStart = {
isAnimationRunning = true
},
onEnd = {
isAnimationRunning = false
}
)
}.start()
}
init {
if (prefs.enableVanced) {
if (isRoot) {
dataModels.add(viewModel.vancedRootModel.value)
} else {
dataModels.add(viewModel.vancedModel.value)
}
apps.add(activity.getString(R.string.vanced))
}
if (prefs.enableMusic) {
if (isRoot) {
dataModels.add(viewModel.musicRootModel.value)
} else {
dataModels.add(viewModel.musicModel.value)
}
apps.add(activity.getString(R.string.music))
}
if (!isRoot) {
dataModels.add(viewModel.microgModel.value)
apps.add(activity.getString(R.string.microg))
}
}
companion object {
const val animationDuration = 250L
}
}

View File

@ -8,13 +8,10 @@ import com.google.firebase.messaging.FirebaseMessaging
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewNotificationSettingBinding
import com.vanced.manager.model.NotifModel
import com.vanced.manager.utils.defPrefs
class GetNotifAdapter(private val context: Context) :
RecyclerView.Adapter<GetNotifAdapter.GetNotifViewHolder>() {
private val prefs = context.defPrefs
private val vanced = NotifModel(
"Vanced-Update",
context.getString(R.string.push_notifications, context.getString(R.string.vanced)),
@ -36,24 +33,15 @@ class GetNotifAdapter(private val context: Context) :
private val apps = arrayOf(vanced, music, microg)
inner class GetNotifViewHolder(val binding: ViewNotificationSettingBinding) :
RecyclerView.ViewHolder(binding.root) {
inner class GetNotifViewHolder(val binding: ViewNotificationSettingBinding) : RecyclerView.ViewHolder(binding.root) {
val switch = binding.notifSwitch
fun bind(position: Int) {
val app = apps[position]
with(binding.notifSwitch) {
setKey(app.key)
setSummary(app.switchSummary)
setTitle(app.switchTitle)
setKey(apps[position].key)
setSummary(apps[position].switchSummary)
setTitle(apps[position].switchTitle)
setDefaultValue(true)
with(prefs) {
setChecked(
getBoolean(
"enable_" + app.key.substringBefore("_"),
true
) && getBoolean(app.key, true)
)
}
}
}
}

View File

@ -57,14 +57,13 @@ class LinkAdapter(
val links = arrayOf(instagram, youtube, github, website, telegram, twitter, discord, reddit)
inner class LinkViewHolder(private val binding: ViewSocialLinkBinding) :
RecyclerView.ViewHolder(binding.root) {
inner class LinkViewHolder(private val binding: ViewSocialLinkBinding) : RecyclerView.ViewHolder(binding.root) {
val logo = binding.linkImage
fun bind(position: Int) {
binding.linkBg.setOnClickListener {
viewModel.openUrl(context, links[position].linkUrl)
viewModel.openUrl(links[position].linkUrl)
}
}
}

View File

@ -18,22 +18,21 @@ class SelectAppsAdapter(private val context: Context) :
private val vanced = SelectAppModel(
context.getString(R.string.vanced),
context.getString(R.string.description_vanced),
context.getString(R.string.select_apps_vanced),
"vanced",
prefs.enableVanced
)
private val music = SelectAppModel(
context.getString(R.string.music),
context.getString(R.string.description_vanced_music),
context.getString(R.string.select_apps_music),
"music",
prefs.enableMusic
)
val apps = arrayOf(vanced, music)
inner class SelectAppsViewHolder(binding: ViewAppCheckboxBinding) :
RecyclerView.ViewHolder(binding.root) {
inner class SelectAppsViewHolder(binding: ViewAppCheckboxBinding) : RecyclerView.ViewHolder(binding.root) {
val appName = binding.appCheckboxText
val appDescription = binding.appCheckboxDescription
val appCard = binding.appCheckboxBg

View File

@ -9,19 +9,15 @@ import com.vanced.manager.R
import com.vanced.manager.databinding.ViewSponsorBinding
import com.vanced.manager.model.SponsorModel
import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.utils.LIGHT
import com.vanced.manager.utils.currentTheme
class SponsorAdapter(
private val context: Context,
private val viewModel: HomeViewModel
private val viewModel: HomeViewModel,
//private val json: ObservableField<JsonObject?>
) : RecyclerView.Adapter<SponsorAdapter.LinkViewHolder>() {
private val brave = SponsorModel(
if (currentTheme == LIGHT) AppCompatResources.getDrawable(
context,
R.drawable.ic_brave_light
) else AppCompatResources.getDrawable(context, R.drawable.ic_brave),
AppCompatResources.getDrawable(context, R.drawable.ic_brave),
"Brave",
BRAVE
)
@ -42,7 +38,7 @@ class SponsorAdapter(
with(binding) {
sponsorName.text = sponsors[position].name
cardSponsor.setOnClickListener {
viewModel.openUrl(context, sponsors[position].url)
viewModel.openUrl(sponsors[position].url)
}
}
}

View File

@ -1,23 +0,0 @@
package com.vanced.manager.adapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.vanced.manager.ui.fragments.GrantRootFragment
import com.vanced.manager.ui.fragments.SelectAppsFragment
import com.vanced.manager.ui.fragments.WelcomeFragment
class WelcomePageAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> WelcomeFragment()
1 -> SelectAppsFragment()
2 -> GrantRootFragment()
else -> throw IllegalArgumentException("Unknown fragment")
}
}
}

View File

@ -1,18 +1,21 @@
package com.vanced.manager.core
import android.app.Application
import android.content.res.Configuration
import android.util.Log
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.topjohnwu.superuser.Shell
import com.vanced.manager.BuildConfig
import com.crowdin.platform.Crowdin
import com.crowdin.platform.CrowdinConfig
import com.crowdin.platform.data.model.AuthConfig
import com.crowdin.platform.data.remote.NetworkType
import com.vanced.manager.BuildConfig.*
import com.vanced.manager.utils.loadJson
import com.vanced.manager.utils.managerAccent
import com.vanced.manager.utils.mutableAccentColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class App : Application() {
open class App: Application() {
private val prefs by lazy { getDefaultSharedPreferences(this) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -20,15 +23,30 @@ class App : Application() {
override fun onCreate() {
scope.launch { loadJson(this@App) }
super.onCreate()
mutableAccentColor.value = prefs.managerAccent
Shell.enableVerboseLogging = BuildConfig.DEBUG
Shell.setDefaultBuilder(
Shell.Builder
.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
//.setInitializers(BusyBoxInstaller::class.java) //TODO fix busybox
.setTimeout(10)
Crowdin.init(this,
CrowdinConfig.Builder().apply {
withDistributionHash(CROWDIN_HASH)
withNetworkType(NetworkType.WIFI)
if (ENABLE_CROWDIN_AUTH) {
if (prefs.getBoolean("crowdin_real_time", false))
withRealTimeUpdates()
withSourceLanguage("en")
withAuthConfig(AuthConfig(CROWDIN_CLIENT_ID, CROWDIN_CLIENT_SECRET, null))
withScreenshotEnabled()
Log.d("test", "crowdin credentials")
}
}.build()
)
if (prefs.getBoolean("crowdin_upload_screenshot", false))
Crowdin.registerScreenShotContentObserver(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Crowdin.onConfigurationChanged()
}
}

View File

@ -1,37 +0,0 @@
package com.vanced.manager.core
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
/**
* CombinedLiveData is a helper class to combine results from two LiveData sources.
* @param combine Function reference that will be used to combine all LiveData data.
* @param R The type of data returned after combining all LiveData data.
* Usage:
* CombinedLiveData(
* getLiveData1(),
* getLiveData2()
* ) { data1, data2 ->
* // Use datas[0], datas[1], ..., datas[N] to return a value
* }
*/
class CombinedLiveData<R, A, B>(
liveDataA: LiveData<A>,
liveDataB: LiveData<B>,
private val combine: (a: A?, b: B?) -> R
) : MediatorLiveData<R>() {
private var a: A? = null
private var b: B? = null
init {
addSource(liveDataA) {
a = it
value = combine(a, b)
}
addSource(liveDataB) {
b = it
value = combine(a, b)
}
}
}

View File

@ -2,9 +2,11 @@ package com.vanced.manager.core.downloader
import android.content.Context
import com.vanced.manager.R
import com.vanced.manager.utils.*
import com.vanced.manager.utils.DownloadHelper.download
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import com.vanced.manager.utils.PackageHelper.install
import com.vanced.manager.utils.baseInstallUrl
import com.vanced.manager.utils.microg
object MicrogDownloader {
@ -16,14 +18,14 @@ object MicrogDownloader {
download(url, "$baseInstallUrl/", folderName, fileName, context, onDownloadComplete = {
startMicrogInstall(context)
}, onError = {
downloadingFile.postValue(context.getString(R.string.error_downloading, fileName))
downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, fileName))
})
}
fun startMicrogInstall(context: Context) {
installing.postValue(true)
postReset()
downloadProgress.value?.installing?.postValue(true)
downloadProgress.value?.postReset()
install("${context.getExternalFilesDir(folderName)}/$fileName", context)
}
}

View File

@ -6,6 +6,7 @@ import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.musicRootPkg
import com.vanced.manager.utils.AppUtils.validateTheme
import com.vanced.manager.utils.DownloadHelper.download
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import com.vanced.manager.utils.PackageHelper.downloadStockCheck
import com.vanced.manager.utils.PackageHelper.install
import com.vanced.manager.utils.PackageHelper.installMusicRoot
@ -13,21 +14,19 @@ import com.vanced.manager.utils.PackageHelper.installMusicRoot
object MusicDownloader {
private var variant: String? = null
private var musicVersion: String? = null
private var version: String? = null
private var versionCode: Int? = null
private var baseurl = ""
private var folderName: String? = null
private var downloadPath: String? = null
private var hashUrl: String? = null
fun downloadMusic(context: Context, version: String? = null) {
fun downloadMusic(context: Context) {
val prefs = context.defPrefs
musicVersion = version ?: prefs.musicVersion?.getLatestAppVersion(
musicVersions.value?.value ?: listOf("")
)
version = prefs.musicVersion?.getLatestAppVersion(musicVersions.value?.value ?: listOf(""))
versionCode = music.value?.int("versionCode")
variant = prefs.managerVariant
baseurl = "$baseInstallUrl/music/v$musicVersion"
baseurl = "$baseInstallUrl/music/v$version"
folderName = "music/$variant"
downloadPath = context.getExternalFilesDir(folderName)?.path
hashUrl = "$baseurl/hash.json"
@ -37,48 +36,36 @@ object MusicDownloader {
private fun downloadApk(context: Context, apk: String = "music") {
val url = if (apk == "stock") "$baseurl/stock/${getArch()}.apk" else "$baseurl/$variant.apk"
download(
url,
"$baseurl/",
folderName!!,
getFileNameFromUrl(url),
context,
onDownloadComplete = {
if (variant == "root" && apk != "stock") {
downloadApk(context, "stock")
return@download
}
download(url, "$baseurl/", folderName!!, getFileNameFromUrl(url), context, onDownloadComplete = {
if (variant == "root" && apk != "stock") {
downloadApk(context, "stock")
return@download
}
when (apk) {
"music" -> {
if (variant == "root") {
if (validateTheme(downloadPath!!, "root", hashUrl!!, context)) {
if (downloadStockCheck(musicRootPkg, versionCode!!, context))
downloadApk(context, "stock")
else
startMusicInstall(context)
} else {
downloadApk(context, apk)
}
} else
startMusicInstall(context)
}
"stock" -> startMusicInstall(context)
when (apk) {
"music" -> {
if (variant == "root") {
if (validateTheme(downloadPath!!, "root", hashUrl!!, context)) {
if (downloadStockCheck(musicRootPkg, versionCode!!, context))
downloadApk(context, "stock")
else
startMusicInstall(context)
} else {
downloadApk(context, apk)
}
} else
startMusicInstall(context)
}
},
onError = {
downloadingFile.postValue(
context.getString(
R.string.error_downloading,
getFileNameFromUrl(url)
)
)
})
"stock" -> startMusicInstall(context)
}
}, onError = {
downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, getFileNameFromUrl(url)))
})
}
fun startMusicInstall(context: Context) {
installing.postValue(true)
postReset()
downloadProgress.value?.installing?.postValue(true)
downloadProgress.value?.postReset()
if (variant == "root")
installMusicRoot(context)
else

View File

@ -2,21 +2,22 @@ package com.vanced.manager.core.downloader
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.logEvent
import com.vanced.manager.R
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.AppUtils.validateTheme
import com.vanced.manager.utils.AppUtils.vancedRootPkg
import com.vanced.manager.utils.DownloadHelper.download
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import com.vanced.manager.utils.PackageHelper.downloadStockCheck
import com.vanced.manager.utils.PackageHelper.installSplitApkFiles
import com.vanced.manager.utils.PackageHelper.installVanced
import com.vanced.manager.utils.PackageHelper.installVancedRoot
import java.io.File
object VancedDownloader {
private lateinit var prefs: SharedPreferences
private lateinit var defPrefs: SharedPreferences
private lateinit var arch: String
@ -47,9 +48,7 @@ object VancedDownloader {
lang = it.split(", ").toMutableList()
}
theme = prefs.theme
vancedVersion = version ?: defPrefs.vancedVersion?.getLatestAppVersion(
vancedVersions.value?.value ?: listOf("")
)
vancedVersion = version ?: defPrefs.vancedVersion?.getLatestAppVersion(vancedVersions.value?.value ?: listOf(""))
themePath = "$baseInstallUrl/apks/v$vancedVersion/$variant/Theme"
hashUrl = "apks/v$vancedVersion/$variant/Theme/hash.json"
arch = getArch()
@ -59,8 +58,8 @@ object VancedDownloader {
try {
downloadSplits(context)
} catch (e: Exception) {
log("VMDownloader", e.stackTraceToString())
downloadingFile.postValue(context.getString(R.string.error_downloading, "Vanced"))
Log.d("VMDownloader", e.stackTraceToString())
downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, "Vanced"))
}
}
@ -70,73 +69,58 @@ object VancedDownloader {
"theme" -> "$themePath/$theme.apk"
"arch" -> "$baseInstallUrl/apks/v$vancedVersion/$variant/Arch/split_config.$arch.apk"
"stock" -> "$themePath/stock.apk"
"dpi" -> "$themePath/dpi.apk"
"dpi" -> "$themePath/dpi.apk"
"lang" -> "$baseInstallUrl/apks/v$vancedVersion/$variant/Language/split_config.${lang[count]}.apk"
else -> throw NotImplementedError("This type of APK is NOT valid. What the hell did you even do?")
}
download(
url,
"$baseInstallUrl/",
folderName!!,
getFileNameFromUrl(url),
context,
onDownloadComplete = {
when (type) {
"theme" ->
if (variant == "root") {
if (validateTheme(downloadPath!!, theme!!, hashUrl, context)) {
if (downloadStockCheck(vancedRootPkg, vancedVersionCode, context))
downloadSplits(context, "arch")
else
startVancedInstall(context)
} else
downloadSplits(context, "theme")
download(url, "$baseInstallUrl/", folderName!!, getFileNameFromUrl(url), context, onDownloadComplete = {
when (type) {
"theme" ->
if (variant == "root") {
if (validateTheme(downloadPath!!, theme!!, hashUrl, context)) {
if (downloadStockCheck(vancedRootPkg, vancedVersionCode, context))
downloadSplits(context, "arch")
else
startVancedInstall(context)
} else
downloadSplits(context, "arch")
"arch" -> if (variant == "root") downloadSplits(
context,
"stock"
) else downloadSplits(context, "lang")
"stock" -> downloadSplits(context, "dpi")
"dpi" -> downloadSplits(context, "lang")
"lang" -> {
count++
succesfulLangCount++
if (count < lang.size)
downloadSplits(context, "lang")
else
startVancedInstall(context)
}
}
},
onError = {
if (type == "lang") {
downloadSplits(context, "theme")
} else
downloadSplits(context, "arch")
"arch" -> if (variant == "root") downloadSplits(context, "stock") else downloadSplits(context, "lang")
"stock" -> downloadSplits(context, "dpi")
"dpi" -> downloadSplits(context, "lang")
"lang" -> {
count++
when {
count < lang.size -> downloadSplits(context, "lang")
succesfulLangCount == 0 -> {
lang.add("en")
downloadSplits(context, "lang")
}
else -> startVancedInstall(context)
}
} else {
downloadingFile.postValue(
context.getString(
R.string.error_downloading,
getFileNameFromUrl(url)
)
)
succesfulLangCount++
if (count < lang.size)
downloadSplits(context, "lang")
else
startVancedInstall(context)
}
})
}
}, onError = {
if (type == "lang") {
count++
when {
count < lang.size -> downloadSplits(context, "lang")
succesfulLangCount == 0 -> {
lang.add("en")
downloadSplits(context, "lang")
}
else -> startVancedInstall(context)
}
} else {
downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, getFileNameFromUrl(url)))
}
})
}
fun startVancedInstall(context: Context, variant: String? = this.variant) {
installing.postValue(true)
postReset()
downloadProgress.value?.installing?.postValue(true)
downloadProgress.value?.postReset()
FirebaseAnalytics.getInstance(context).logEvent(FirebaseAnalytics.Event.SELECT_ITEM) {
variant?.let { param("vanced_variant", it) }
theme?.let { param("vanced_theme", it) }
@ -144,6 +128,6 @@ object VancedDownloader {
if (variant == "root")
installVancedRoot(context)
else
installSplitApkFiles(context, "vanced")
installVanced(context)
}
}

View File

@ -1,12 +1,13 @@
package com.vanced.manager.core.firebase
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.vanced.manager.utils.AppUtils.log
class CloudMessaging : FirebaseMessagingService() {
override fun onNewToken(p0: String) {
log("VMC", "Generated new token: $p0")
super.onNewToken(p0)
Log.d("VMC", "Generated new token: $p0")
}
}

View File

@ -4,35 +4,33 @@ import android.app.Service
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.IBinder
import com.vanced.manager.utils.AppUtils.log
import android.util.Log
import com.vanced.manager.utils.AppUtils.sendCloseDialog
import com.vanced.manager.utils.AppUtils.sendFailure
import com.vanced.manager.utils.AppUtils.sendRefresh
class AppInstallerService : Service() {
class AppInstallerService: Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
log(TAG, "Requesting user confirmation for installation")
Log.d(TAG, "Requesting user confirmation for installation")
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
startActivity(confirmationIntent)
} catch (e: Exception) {
log("VMInstall", "Unable to start installation")
Log.d("VMInstall", "Unable to start installation")
}
}
PackageInstaller.STATUS_SUCCESS -> {
log(TAG, "Installation succeed")
Log.d(TAG, "Installation succeed")
sendCloseDialog(this)
sendRefresh(this)
}
else -> {
sendCloseDialog(this)
intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)?.let {
sendFailure(it, this)
}
sendFailure(intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999), intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE),this)
}
}
stopSelf()

View File

@ -4,16 +4,16 @@ import android.app.Service
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.IBinder
import com.vanced.manager.utils.AppUtils.log
import android.util.Log
import com.vanced.manager.utils.AppUtils.sendRefresh
class AppUninstallerService : Service() {
class AppUninstallerService: Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val pkgName = intent?.getStringExtra("pkg")
when (intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
log(AppInstallerService.TAG, "Requesting user confirmation for uninstallation")
Log.d(AppInstallerService.TAG, "Requesting user confirmation for uninstallation")
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
@ -24,11 +24,11 @@ class AppUninstallerService : Service() {
//Delay broadcast until activity (and fragment) show up on the screen
PackageInstaller.STATUS_SUCCESS -> {
sendRefresh(this)
log("VMpm", "Successfully uninstalled $pkgName")
Log.d("VMpm", "Successfully uninstalled $pkgName")
}
PackageInstaller.STATUS_FAILURE -> {
sendRefresh(this)
log("VMpm", "Failed to uninstall $pkgName")
Log.d("VMpm", "Failed to uninstall $pkgName")
}
}
stopSelf()

View File

@ -1,10 +0,0 @@
package com.vanced.manager.model
import androidx.annotation.DrawableRes
import com.vanced.manager.R
enum class ButtonTag(@DrawableRes val image: Int) {
INSTALL(R.drawable.ic_app_download),
UPDATE(R.drawable.ic_app_update),
REINSTALL(R.drawable.ic_app_reinstall)
}

View File

@ -3,79 +3,88 @@ package com.vanced.manager.model
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.lifecycle.*
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.beust.klaxon.JsonObject
import com.vanced.manager.R
import com.vanced.manager.core.CombinedLiveData
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
open class DataModel(
jsonObject: LiveData<JsonObject?>,
context: Context,
private val jsonObject: LiveData<JsonObject?>,
private val context: Context,
lifecycleOwner: LifecycleOwner,
val appPkg: String,
val appName: String,
val appDescription: String,
@DrawableRes val appIcon: Int
val appIcon: Drawable?,
) {
val isAppInstalled = Transformations.map(jsonObject) { isAppInstalled(appPkg) }
private val versionCode = MutableLiveData<Int>()
private val installedVersionCode = MutableLiveData<Int>()
private val versionCode = Transformations.map(jsonObject) { jobj ->
jobj?.int("versionCode") ?: 0
}
private val installedVersionCode = Transformations.map(isAppInstalled) {
getPkgVersionCode(appPkg, it)
}
private val unavailable = context.getString(R.string.unavailable)
private val pm = context.packageManager
val isAppInstalled = MutableLiveData<Boolean>()
val versionName = MutableLiveData<String>()
val installedVersionName = MutableLiveData<String>()
val buttonTxt = MutableLiveData<String>()
val changelog = MutableLiveData<String>()
val versionName = Transformations.map(jsonObject) { jobj ->
jobj?.string("version") ?: unavailable
}
val changelog = Transformations.map(jsonObject) { jobj ->
jobj?.string("changelog") ?: unavailable
}
val installedVersionName = Transformations.map(isAppInstalled) {
getPkgVersionName(appPkg, it)
}
val buttonTag = CombinedLiveData(versionCode, installedVersionCode) { versionCode, installedVersionCode ->
compareInt(installedVersionCode, versionCode)
private fun fetch() {
val jobj = jsonObject.value
isAppInstalled.value = isAppInstalled(appPkg)
versionCode.value = jobj?.int("versionCode") ?: 0
versionName.value = jobj?.string("version")?.removeSuffix("-vanced") ?: context.getString(R.string.unavailable)
changelog.value = jobj?.string("changelog") ?: context.getString(R.string.unavailable)
}
open fun isAppInstalled(pkg: String): Boolean = isPackageInstalled(pkg, pm)
init {
fetch()
with(lifecycleOwner) {
jsonObject.observe(this) {
fetch()
}
isAppInstalled.observe(this) {
installedVersionCode.value = getPkgVersionCode(appPkg)
installedVersionName.value = getPkgVersionName(appPkg)
}
versionCode.observe(this) { versionCode ->
installedVersionCode.observe(this) { installedVersionCode ->
buttonTxt.value = compareInt(installedVersionCode, versionCode)
}
}
}
}
private fun getPkgVersionName(pkg: String, isAppInstalled: Boolean): String {
return if (isAppInstalled) {
pm?.getPackageInfo(pkg, 0)?.versionName?.removeSuffix("-vanced") ?: unavailable
open fun isAppInstalled(pkg: String): Boolean = isPackageInstalled(pkg, context.packageManager)
private fun getPkgVersionName(pkg: String): String {
val pm = context.packageManager
return if (isAppInstalled.value == true) {
pm?.getPackageInfo(pkg, 0)?.versionName?.removeSuffix("-vanced") ?: context.getString(R.string.unavailable)
} else {
unavailable
context.getString(R.string.unavailable)
}
}
@Suppress("DEPRECATION")
private fun getPkgVersionCode(pkg: String, isAppInstalled: Boolean): Int {
return if (isAppInstalled) {
private fun getPkgVersionCode(pkg: String): Int {
val pm = context.packageManager
return if (isAppInstalled.value == true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
pm?.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt() ?: 0
else
pm?.getPackageInfo(pkg, 0)?.versionCode ?: 0
} else {
0
}
} else 0
}
private fun compareInt(int1: Int?, int2: Int?): ButtonTag {
private fun compareInt(int1: Int?, int2: Int?): String {
if (int2 != null && int1 != null) {
return when {
int1 == 0 -> ButtonTag.INSTALL
int2 > int1 -> ButtonTag.UPDATE
int1 >= int2 -> ButtonTag.REINSTALL
else -> ButtonTag.INSTALL
int1 == 0 -> context.getString(R.string.install)
int2 > int1 -> context.getString(R.string.update)
int2 == int1 || int1 > int2 -> context.getString(R.string.button_reinstall)
else -> context.getString(R.string.install)
}
}
return ButtonTag.INSTALL
return context.getString(R.string.install)
}
}

View File

@ -4,5 +4,5 @@ import android.graphics.drawable.Drawable
data class LinkModel(
val linkIcon: Drawable?,
val linkUrl: String
val linkUrl: String,
)

View File

@ -1,8 +1,8 @@
package com.vanced.manager.model
data class NotifModel(
val topic: String,
val switchTitle: String,
val switchSummary: String,
val key: String
val topic: String,
val switchTitle: String,
val switchSummary: String,
val key: String
)

View File

@ -0,0 +1,30 @@
package com.vanced.manager.model
import androidx.lifecycle.MutableLiveData
import okhttp3.ResponseBody
import retrofit2.Call
open class ProgressModel {
val downloadProgress = MutableLiveData<Int>()
val downloadingFile = MutableLiveData<String>()
val installing = MutableLiveData<Boolean>()
var currentDownload: Call<ResponseBody>? = null
fun reset() {
downloadProgress.value = 0
downloadingFile.value = ""
}
fun postReset() {
downloadProgress.postValue(0)
downloadingFile.postValue("")
}
init {
installing.postValue(false)
reset()
}
}

View File

@ -1,7 +1,7 @@
package com.vanced.manager.model
import android.content.Context
import androidx.annotation.DrawableRes
import android.graphics.drawable.Drawable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import com.beust.klaxon.JsonObject
@ -10,18 +10,18 @@ import com.vanced.manager.utils.PackageHelper
class RootDataModel(
jsonObject: LiveData<JsonObject?>,
context: Context,
lifecycleOwner: LifecycleOwner,
appPkg: String,
appName: String,
appDescription: String,
@DrawableRes appIcon: Int,
appIcon: Drawable?,
//BUG THIS!
//kotlin thinks that this value is null if we use
//private val scriptName: String
//Although it's impossible for it to be null.
//Ironic, isn't it?
private val scriptName: String?
) : DataModel(
jsonObject, context, appPkg, appName, appDescription, appIcon
): DataModel(
jsonObject, context, lifecycleOwner, appPkg, appName, appIcon
) {
override fun isAppInstalled(pkg: String): Boolean {

View File

@ -3,7 +3,7 @@ package com.vanced.manager.model
import android.graphics.drawable.Drawable
data class SponsorModel(
val logo: Drawable?,
val name: String,
val url: String
val logo: Drawable?,
val name: String,
val url: String
)

View File

@ -1,12 +1,10 @@
package com.vanced.manager.ui
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
@ -15,7 +13,10 @@ import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.crowdin.platform.LoadingStateListener
import com.google.firebase.messaging.FirebaseMessaging
import com.vanced.manager.BuildConfig.ENABLE_CROWDIN_AUTH
import com.vanced.manager.BuildConfig.VERSION_CODE
import com.vanced.manager.R
import com.vanced.manager.databinding.ActivityMainBinding
@ -25,21 +26,30 @@ import com.vanced.manager.ui.dialogs.URLChangeDialog
import com.vanced.manager.ui.fragments.HomeFragmentDirections
import com.vanced.manager.ui.fragments.SettingsFragmentDirections
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.currentLocale
import com.vanced.manager.utils.AppUtils.faqpkg
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.AppUtils.playStorePkg
import com.vanced.manager.utils.AppUtils.vancedRootPkg
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private val navHost by lazy { findNavController(R.id.nav_host) }
private val loadingObserver = object : LoadingStateListener {
val tag = "VMLocalisation"
override fun onDataChanged() {
Log.d(tag, "Loaded data")
}
override fun onFailure(throwable: Throwable) {
Log.d(tag, "Failed to load data: $throwable")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
setFinalTheme()
super.onCreate(savedInstanceState)
if (ENABLE_CROWDIN_AUTH)
authCrowdin()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -58,7 +68,7 @@ class MainActivity : AppCompatActivity() {
initDialogs(intent.getBooleanExtra("firstLaunch", false))
manager.observe(this) {
if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) {
ManagerUpdateDialog.newInstance(true).show(this)
ManagerUpdateDialog.newInstance(false).show(this)
}
}
}
@ -69,35 +79,36 @@ class MainActivity : AppCompatActivity() {
}
private fun setDisplayHomeAsUpEnabled(isNeeded: Boolean) {
binding.toolbar.navigationIcon = if (isNeeded) ContextCompat.getDrawable(
this,
R.drawable.ic_keyboard_backspace_black_24dp
) else null
binding.toolbar.navigationIcon = if (isNeeded) ContextCompat.getDrawable(this, R.drawable.ic_keyboard_backspace_black_24dp) else null
}
override fun onPause() {
super.onPause()
Crowdin.unregisterDataLoadingObserver(loadingObserver)
}
override fun onResume() {
setFinalTheme()
super.onResume()
Crowdin.registerDataLoadingObserver(loadingObserver)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
when (item.itemId) {
android.R.id.home -> {
onBackPressedDispatcher.onBackPressed()
return true
}
R.id.toolbar_about -> {
navHost.navigate(HomeFragmentDirections.toAboutFragment())
true
return true
}
R.id.toolbar_settings -> {
navHost.navigate(HomeFragmentDirections.toSettingsFragment())
true
}
R.id.toolbar_log -> {
navHost.navigate(HomeFragmentDirections.toLogFragment())
true
return true
}
R.id.toolbar_update_manager -> {
ManagerUpdateDialog.newInstance(false)
.show(supportFragmentManager, "manager_update")
true
ManagerUpdateDialog.newInstance(false).show(supportFragmentManager, "manager_update")
}
R.id.dev_settings -> {
navHost.navigate(SettingsFragmentDirections.toDevSettingsFragment())
@ -105,34 +116,22 @@ class MainActivity : AppCompatActivity() {
}
else -> super.onOptionsItemSelected(item)
}
return false
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase))
super.attachBaseContext(Crowdin.wrapContext(LanguageContextWrapper.wrap(newBase)))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
onActivityResult(requestCode)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
//update manager language when system language is changed
@Suppress("DEPRECATION")
if (newConfig.locale != currentLocale) {
recreate() //restarting activity in order to recreate viewmodels, otherwise some text won't update
return
}
when (newConfig.orientation) {
Configuration.ORIENTATION_PORTRAIT -> log(
"VMUI",
"screen orientation changed to portrait"
)
Configuration.ORIENTATION_LANDSCAPE -> log(
"VMUI",
"screen orientation changed to landscape"
)
else -> log("VMUI", "screen orientation changed to [REDACTED]")
}
recreate() //restarting activity to recreate viewmodels, otherwise some text won't update
}
override fun recreate() {
@ -154,30 +153,26 @@ class MainActivity : AppCompatActivity() {
urldialog.show(this)
}
if (firstLaunch) {
DialogContainer.showSecurityDialog(this)
with(FirebaseMessaging.getInstance()) {
subscribeToTopic("Vanced-Update")
subscribeToTopic("Music-Update")
subscribeToTopic("MicroG-Update")
when {
firstLaunch -> {
DialogContainer.showSecurityDialog(this)
with(FirebaseMessaging.getInstance()) {
subscribeToTopic("Vanced-Update")
subscribeToTopic("Music-Update")
subscribeToTopic("MicroG-Update")
}
}
} else {
if (isMiuiOptimizationsEnabled) {
DialogContainer.miuiDialog(this)
}
}
if (!prefs.getBoolean("statement", true)) {
DialogContainer.statementFalse(this)
}
if (variant == "root") {
if (PackageHelper.getPackageVersionName(vancedRootPkg, packageManager) == "14.21.54") {
DialogContainer.basicDialog(
getString(R.string.hold_on),
getString(R.string.magisk_vanced),
this
)
!prefs.getBoolean("statement", true) -> DialogContainer.statementFalse(this)
variant == "root" -> {
if (PackageHelper.getPackageVersionName(
"com.google.android.youtube",
packageManager
) == "14.21.54")
DialogContainer.basicDialog(
getString(R.string.hold_on),
getString(R.string.magisk_vanced),
this
)
}
}
}

View File

@ -1,26 +0,0 @@
package com.vanced.manager.ui
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.topjohnwu.superuser.Shell
class SplashScreenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Preheat the shell
Shell.getShell {
if (getDefaultSharedPreferences(this).getBoolean("firstLaunch", true)) {
startActivity(Intent(this, WelcomeActivity::class.java))
finish()
} else {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}
}

View File

@ -1,86 +1,21 @@
package com.vanced.manager.ui
import android.animation.ValueAnimator
import android.os.Bundle
import android.util.LayoutDirection
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.addListener
import androidx.viewpager2.widget.ViewPager2
import com.vanced.manager.adapter.WelcomePageAdapter
import com.vanced.manager.databinding.ActivityWelcomeBinding
import kotlin.math.abs
import androidx.navigation.findNavController
import com.vanced.manager.R
class WelcomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityWelcomeBinding
private var isRtl = false
private val navHost by lazy { findNavController(R.id.welcome_navhost) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding.root)
isRtl = resources.configuration.layoutDirection == LayoutDirection.RTL
binding.welcomeViewpager.apply {
adapter = WelcomePageAdapter(this@WelcomeActivity)
isUserInputEnabled = false
setPageTransformer { page, position ->
page.apply {
val pageWidth = width.toFloat()
//Thank you, fragula dev!
when {
position > 0 && position < 1 -> {
alpha = 1f
translationX = 0f
}
position > -1 && position <= 0 -> {
alpha = 1.0f - abs(position * 0.7f)
translationX = pageWidth.rtlCompat * position / 1.3F
}
}
}
}
}
setContentView(R.layout.activity_welcome)
}
override fun onBackPressed() {
with(binding) {
if (welcomeViewpager.currentItem == 0) {
super.onBackPressed()
} else {
navigateTo(welcomeViewpager.currentItem - 1)
}
}
if (!navHost.popBackStack())
finish()
}
fun navigateTo(position: Int) {
binding.welcomeViewpager.currentPosition = position
}
private val Float.rtlCompat get() = if (isRtl) this else -this
//Shit way to implement animation duration, but at least it works
private var ViewPager2.currentPosition: Int
get() = currentItem
set(value) {
val pixelsToDrag: Int = width * (value - currentItem)
val animator = ValueAnimator.ofInt(0, pixelsToDrag)
var previousValue = 0
animator.apply {
addUpdateListener { valueAnimator ->
val currentValue = valueAnimator.animatedValue as Int
val currentPxToDrag = (currentValue - previousValue).toFloat()
fakeDragBy(currentPxToDrag.rtlCompat)
previousValue = currentValue
}
addListener(
onStart = { beginFakeDrag() },
onEnd = { endFakeDrag() }
)
duration = 500
start()
}
}
}

View File

@ -1,2 +0,0 @@
package com.vanced.manager.ui.compose

View File

@ -1,107 +0,0 @@
package com.vanced.manager.ui.compose
//import androidx.compose.foundation.clickable
//import androidx.compose.foundation.layout.Column
//import androidx.compose.foundation.layout.ColumnScope
//import androidx.compose.foundation.layout.padding
//import androidx.compose.material.Switch
//import androidx.compose.material.Text
//import androidx.compose.runtime.*
//import androidx.compose.ui.Modifier
//import androidx.compose.ui.graphics.Color
//import androidx.compose.ui.platform.LocalContext
//import androidx.compose.ui.tooling.preview.Preview
//import androidx.compose.ui.unit.dp
//import androidx.compose.ui.unit.em
//import androidx.compose.ui.unit.sp
//import androidx.constraintlayout.compose.ConstraintLayout
//import androidx.core.content.edit
//import androidx.preference.PreferenceManager
//
//@Composable
//@Preview
//inline fun PreferenceCategory(
// categoryTitle: String,
// content: @Composable ColumnScope.() -> Unit
//) {
// Column {
// Text(
// categoryTitle,
// letterSpacing = 0.15.em,
// color = Color(LocalContext.current.accentColor)
// )
// content()
// }
//}
//
//
//@Composable
//@Preview
//inline fun SwitchPreference(
// preferenceTitle: String,
// preferenceDescription: String? = null,
// preferenceKey: String,
// defValue: Boolean = true,
// crossinline onCheckedChange: (Boolean) -> Unit = {}
//) {
// val prefs = PreferenceManager.getDefaultSharedPreferences(LocalContext.current)
// val isChecked = remember { mutableStateOf(prefs.getBoolean(preferenceKey, defValue)) }
// ConstraintLayout(modifier = Modifier.padding(12.dp, 4.dp).clickable {
// isChecked.value = !isChecked.value
// }) {
// val (title, description, switch) = createRefs()
// Text(preferenceTitle, fontSize = 16.sp, modifier = Modifier.constrainAs(title) {
// top.linkTo(parent.top)
// start.linkTo(parent.start)
// end.linkTo(switch.start, 4.dp)
// if (preferenceDescription != null) {
// bottom.linkTo(description.top)
// } else {
// bottom.linkTo(parent.bottom)
// }
// })
// if (preferenceDescription != null) {
// Text(preferenceDescription, fontSize = 13.sp, modifier = Modifier.constrainAs(description) {
// top.linkTo(title.bottom)
// start.linkTo(parent.start)
// end.linkTo(switch.start, 8.dp)
// })
// }
// Switch(
// isChecked.value,
// onCheckedChange = {
// prefs.edit { putBoolean(preferenceKey, it) }
// onCheckedChange(it)
// },
// modifier = Modifier.clickable(false) {}
// )
// }
//}
//
//@Composable
//@Preview
//fun Preference(
// preferenceTitle: String,
// preferenceDescription: String? = null,
// onClick: () -> Unit
//) {
// ConstraintLayout(modifier = Modifier.padding(12.dp, 4.dp).clickable(onClick = onClick)) {
// val (title, description, switch) = createRefs()
// Text(preferenceTitle, fontSize = 16.sp, modifier = Modifier.constrainAs(title) {
// top.linkTo(parent.top)
// start.linkTo(parent.start)
// end.linkTo(switch.start, 4.dp)
// if (preferenceDescription != null) {
// bottom.linkTo(description.top)
// } else {
// bottom.linkTo(parent.bottom)
// }
// })
// if (preferenceDescription != null) {
// Text(preferenceDescription, fontSize = 13.sp, modifier = Modifier.constrainAs(description) {
// top.linkTo(title.bottom)
// })
// }
// }
//
//}

View File

@ -1,52 +0,0 @@
package com.vanced.manager.ui.compose
//import android.content.Context
//import androidx.compose.foundation.isSystemInDarkTheme
//import androidx.compose.material.MaterialTheme
//import androidx.compose.material.darkColors
//import androidx.compose.material.lightColors
//import androidx.compose.runtime.Composable
//import androidx.compose.runtime.MutableState
//import androidx.compose.runtime.mutableStateOf
//import androidx.compose.runtime.remember
//import androidx.compose.ui.graphics.Color
//import androidx.compose.ui.platform.LocalContext
//import androidx.preference.PreferenceManager.getDefaultSharedPreferences
//import com.vanced.manager.utils.mutableAccentColor
//
//const val Dark = "Dark"
//const val SystemDefault = "System Default"
//
//const val defAccentColor: Int = -13732865
//
//val Context.accentColor get() = mutableAccentColor.value ?: getDefaultSharedPreferences(this).getInt("manager_accent_color", defAccentColor)
//
//enum class Theme {
// DARK, LIGHT
//}
//
//val lightColors = lightColors(
// primary = Color(defAccentColor)
//)
//
//val darkColors = darkColors(
// primary = Color(defAccentColor)
//)
//
//fun Context.retrieveTheme(): Theme = when (getDefaultSharedPreferences(this).getString("manager_theme", SystemDefault)) {
// SystemDefault -> if (isSystemInDarkTheme()) Theme.DARK else Theme.LIGHT
// Dark -> Theme.DARK
// else -> Theme.LIGHT
//}
//
//val Context.isDarkTheme: Boolean
// get() = retrieveTheme() == Theme.DARK
//
//fun Context.ManagerTheme(
// content: @Composable () -> Unit
//) {
// MaterialTheme(
// colors = if (isDarkTheme) darkColors else lightColors,
// content = content
// )
//}

View File

@ -31,7 +31,7 @@ class EmptyPreference @JvmOverloads constructor(
}
fun setSummary(newSummary: String) {
with(binding) {
with (binding) {
preferenceSummary.text = newSummary
preferenceSummary.isVisible = true
preferenceTitle.setPadding(0, 0, 0, 0)
@ -40,11 +40,10 @@ class EmptyPreference @JvmOverloads constructor(
private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs?.let { mAttrs ->
val typedArray =
context.obtainStyledAttributes(mAttrs, R.styleable.EmptyPreference, 0, 0)
val typedArray = context.obtainStyledAttributes(mAttrs, R.styleable.EmptyPreference, 0, 0)
val title = typedArray.getText(R.styleable.EmptyPreference_preference_title)
val summary = typedArray.getText(R.styleable.EmptyPreference_preference_summary)
with(binding) {
with (binding) {
if (summary != null) {
preferenceSummary.text = summary
} else {

View File

@ -10,7 +10,7 @@ import com.vanced.manager.databinding.ViewPreferenceCategoryBinding
class PreferenceCategory @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
defStyle: Int = 0,
) : LinearLayout(context, attrs, defStyle) {
private var _binding: ViewPreferenceCategoryBinding? = null
@ -27,8 +27,7 @@ class PreferenceCategory @JvmOverloads constructor(
private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs.let { mAttrs ->
val typedArray =
context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceCategory, 0, 0)
val typedArray = context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceCategory, 0, 0)
val title = typedArray.getText(R.styleable.PreferenceCategory_category_title)
binding.categoryTitle.text = title

View File

@ -1,7 +1,6 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.CompoundButton
@ -32,13 +31,6 @@ class PreferenceSwitch @JvmOverloads constructor(
private var mListener: OnCheckedListener? = null
private val prefListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == prefKey) {
binding.preferenceSwitch.isChecked = sharedPreferences.getBoolean(key, defValue)
}
}
private var _binding: ViewPreferenceSwitchBinding? = null
val binding: ViewPreferenceSwitchBinding
@ -46,7 +38,6 @@ class PreferenceSwitch @JvmOverloads constructor(
init {
_binding = ViewPreferenceSwitchBinding.inflate(LayoutInflater.from(context), this, true)
prefs.registerOnSharedPreferenceChangeListener(prefListener)
attrs?.let { mAttrs ->
with(context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceSwitch, 0, 0)) {
val title = getText(R.styleable.PreferenceSwitch_switch_title)
@ -94,8 +85,4 @@ class PreferenceSwitch @JvmOverloads constructor(
defValue = newVal
binding.preferenceSwitch.isChecked = prefs.getBoolean(prefKey, newVal)
}
fun setChecked(checked: Boolean) {
binding.preferenceSwitch.isChecked = checked
}
}

View File

@ -0,0 +1,32 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
open class SlidingConstraintLayout : ConstraintLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(
context,
attrs
)
var xFraction: Float
get() {
val width = width
return if (width != 0)
x / getWidth()
else
x
}
set(xFraction) {
val width = width
val newWidth =
if (width > 0)
xFraction * width
else
(1).toFloat()
x = newWidth
}
}

View File

@ -0,0 +1,51 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
open class SlidingLinearLayout: LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(
context,
attrs
)
var yFraction: Float
get() {
val height = height
return if (height != 0)
y / height
else
y
}
set(yFraction) {
val height = height
val newHeight =
if (height > 0)
yFraction * height
else
(1).toFloat()
y = newHeight
}
var xFraction: Float
get() {
val width = width
return if (width != 0)
x / getWidth()
else
x
}
set(xFraction) {
val width = width
val newWidth =
if (width > 0)
xFraction * width
else
(1).toFloat()
x = newWidth
}
}

View File

@ -0,0 +1,32 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
open class SlidingSwipeRefreshLayout : SwipeRefreshLayout {
constructor(context: Context?) : super(context!!)
constructor(context: Context?, attrs: AttributeSet?) : super(
context!!,
attrs
)
var xFraction: Float
get() {
val width = width
return if (width != 0)
x / getWidth()
else
x
}
set(xFraction) {
val width = width
val newWidth =
if (width > 0)
xFraction * width
else
(1).toFloat()
x = newWidth
}
}

View File

@ -0,0 +1,24 @@
package com.vanced.manager.ui.core
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.vanced.manager.ui.MainActivity
import com.vanced.manager.ui.WelcomeActivity
class SplashScreenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getDefaultSharedPreferences(this).getBoolean("firstLaunch", true)) {
startActivity(Intent(this, WelcomeActivity::class.java))
finish()
} else {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}

View File

@ -1,19 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
import com.vanced.manager.utils.accentColor
class ThemedAppCard @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialCardView(context, attributeSet, defStyleAttr) {
init {
setCardBackgroundColor(ColorStateList.valueOf(accentColor.value!!).withAlpha(35))
}
}

View File

@ -1,24 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.widget.Toast
import com.google.android.material.button.MaterialButton
import com.vanced.manager.utils.accentColor
class ThemedIconButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialButton(context, attributeSet, defStyleAttr) {
init {
iconTint = ColorStateList.valueOf(accentColor.value!!)
setOnLongClickListener {
Toast.makeText(context, contentDescription, Toast.LENGTH_SHORT).show()
true
}
}
}

View File

@ -7,7 +7,9 @@ import androidx.core.graphics.ColorUtils
import com.google.android.material.button.MaterialButton
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.lifecycleOwner
import com.vanced.manager.utils.managerAccent
class ThemedMaterialButton @JvmOverloads constructor(
context: Context,
@ -16,7 +18,8 @@ class ThemedMaterialButton @JvmOverloads constructor(
) : MaterialButton(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
setBgColor(context.defPrefs.managerAccent)
context.lifecycleOwner()?.let { owner ->
accentColor.observe(owner) { color ->
setBgColor(color.toInt())
}

View File

@ -5,13 +5,14 @@ import android.content.res.ColorStateList
import android.util.AttributeSet
import com.google.android.material.checkbox.MaterialCheckBox
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerAccent
class ThemedMaterialCheckbox @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : MaterialCheckBox(context, attributeSet, R.attr.checkboxStyle) {
init {
buttonTintList = ColorStateList.valueOf(accentColor.value!!)
buttonTintList = ColorStateList.valueOf(context.defPrefs.managerAccent)
}
}

View File

@ -5,13 +5,14 @@ import android.content.res.ColorStateList
import android.util.AttributeSet
import com.google.android.material.radiobutton.MaterialRadioButton
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerAccent
class ThemedMaterialRadioButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : MaterialRadioButton(context, attributeSet, R.attr.radioButtonStyle) {
init {
buttonTintList = ColorStateList.valueOf(accentColor.value!!)
buttonTintList = ColorStateList.valueOf(context.defPrefs.managerAccent)
}
}

View File

@ -4,20 +4,17 @@ import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import com.google.android.material.slider.Slider
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerAccent
class ThemedMaterialSlider @JvmOverloads constructor(
class ThemedMaterialSlider@JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : Slider(context, attributeSet, defStyleAttr) {
init {
val accentValue = ColorStateList.valueOf(accentColor.value!!)
thumbTintList = accentValue
trackActiveTintList = accentValue
trackInactiveTintList = accentValue.withAlpha(70)
haloTintList = accentValue.withAlpha(60)
thumbStrokeColor = ColorStateList.valueOf(context.defPrefs.managerAccent)
}
}

View File

@ -6,7 +6,10 @@ import android.util.AttributeSet
import androidx.core.graphics.ColorUtils
import com.google.android.material.button.MaterialButton
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.lifecycleOwner
import com.vanced.manager.utils.managerAccent
class ThemedOutlinedMaterialButton @JvmOverloads constructor(
context: Context,
@ -14,7 +17,8 @@ class ThemedOutlinedMaterialButton @JvmOverloads constructor(
defStyleAttr: Int = 0
) : MaterialButton(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
applyAccent(context.defPrefs.managerAccent)
context.lifecycleOwner()?.let { owner ->
accentColor.observe(owner) { color ->
applyAccent(color.toInt())
}
@ -23,9 +27,6 @@ class ThemedOutlinedMaterialButton @JvmOverloads constructor(
private fun applyAccent(color: Int) {
setTextColor(color)
rippleColor = ColorStateList(
arrayOf(intArrayOf()),
intArrayOf(ColorUtils.setAlphaComponent(color, 50))
)
rippleColor = ColorStateList(arrayOf(intArrayOf()), intArrayOf(ColorUtils.setAlphaComponent(color, 50)))
}
}

View File

@ -4,27 +4,21 @@ import android.content.Context
import android.util.AttributeSet
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerAccent
class ThemedSwipeRefreshlayout @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null
) : SwipeRefreshLayout(context, attributeSet) {
init {
setColorSchemeColors(accentColor.value!!)
setColorSchemeColors(context.defPrefs.managerAccent)
initAttrs(context, attributeSet)
}
private fun initAttrs(context: Context, attributeSet: AttributeSet?) {
attributeSet.let {
val typedAttrs =
context.obtainStyledAttributes(it, R.styleable.ThemedSwipeRefreshlayout, 0, 0)
setProgressBackgroundColorSchemeColor(
typedAttrs.getColor(
R.styleable.ThemedSwipeRefreshlayout_progressBackgroundColor,
0
)
)
val typedAttrs = context.obtainStyledAttributes(it, R.styleable.ThemedSwipeRefreshlayout, 0, 0)
setProgressBackgroundColorSchemeColor(typedAttrs.getColor(R.styleable.ThemedSwipeRefreshlayout_progressBackgroundColor, 0))
typedAttrs.recycle()
}
}

View File

@ -9,18 +9,20 @@ import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.DrawableCompat
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.lifecycleOwner
import com.vanced.manager.utils.managerAccent
class ThemedSwitchCompat @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : SwitchCompat(context, attributeSet, R.attr.switchStyle) {
private val states =
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked))
private val states = arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked))
init {
context.lifecycleOwner?.let { owner ->
setSwitchColors(context.defPrefs.managerAccent)
context.lifecycleOwner()?.let { owner ->
accentColor.observe(owner) { color ->
setSwitchColors(color.toInt())
}
@ -30,13 +32,7 @@ class ThemedSwitchCompat @JvmOverloads constructor(
private fun setSwitchColors(color: Int) {
val thumbColors = intArrayOf(Color.LTGRAY, color)
val trackColors = intArrayOf(Color.GRAY, ColorUtils.setAlphaComponent(color, 70))
DrawableCompat.setTintList(
DrawableCompat.wrap(thumbDrawable),
ColorStateList(states, thumbColors)
)
DrawableCompat.setTintList(
DrawableCompat.wrap(trackDrawable),
ColorStateList(states, trackColors)
)
DrawableCompat.setTintList(DrawableCompat.wrap(thumbDrawable), ColorStateList(states, thumbColors))
DrawableCompat.setTintList(DrawableCompat.wrap(trackDrawable), ColorStateList(states, trackColors))
}
}

View File

@ -4,7 +4,9 @@ import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.lifecycleOwner
import com.vanced.manager.utils.managerAccent
class ThemedTextView @JvmOverloads constructor(
context: Context,
@ -12,7 +14,8 @@ class ThemedTextView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : AppCompatTextView(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
setTextColor(context.defPrefs.managerAccent)
context.lifecycleOwner()?.let { owner ->
accentColor.observe(owner) { color ->
setTextColor(color.toInt())
}

View File

@ -1,6 +1,5 @@
package com.vanced.manager.ui.dialogs
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -18,7 +17,8 @@ import com.vanced.manager.core.downloader.MusicDownloader.downloadMusic
import com.vanced.manager.core.downloader.VancedDownloader.downloadVanced
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppDownloadBinding
import com.vanced.manager.utils.*
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import com.vanced.manager.utils.applyAccent
class AppDownloadDialog : BindingDialogFragment<DialogAppDownloadBinding>() {
@ -73,43 +73,39 @@ class AppDownloadDialog : BindingDialogFragment<DialogAppDownloadBinding>() {
appDownloadHeader.text = app
if (arguments?.getBoolean(TAG_INSTALLING) == false) {
when (app) {
getString(R.string.vanced) -> downloadVanced(
requireContext(),
arguments?.getString(TAG_VERSION)
)
getString(R.string.music) -> downloadMusic(
requireContext(),
arguments?.getString(TAG_VERSION)
)
getString(R.string.vanced) -> downloadVanced(requireContext(), arguments?.getString(TAG_VERSION))
getString(R.string.music) -> downloadMusic(requireContext())
getString(R.string.microg) -> downloadMicrog(requireContext())
}
}
}
}
@SuppressLint("SetTextI18n")
private fun DialogAppDownloadBinding.bindDownloadProgress() {
downloadProgress.observe(viewLifecycleOwner) {
appDownloadProgressbar.progress = it
appDownloadProgress.text = "$it%"
}
installing.observe(viewLifecycleOwner) { installing ->
appDownloadProgressbarContainer.isVisible = !installing
appInstallProgressbar.isVisible = installing
appDownloadFile.isVisible = !installing
appDownloadCancel.isEnabled = !installing
appDownloadCancel.setOnClickListener {
if (installing) {
return@setOnClickListener
with(downloadProgress) {
observe(viewLifecycleOwner) { progressModel ->
progressModel.downloadProgress.observe(viewLifecycleOwner) {
appDownloadProgressbar.progress = it
}
progressModel.installing.observe(viewLifecycleOwner) { installing ->
appDownloadProgressbar.isVisible = !installing
appInstallProgressbar.isVisible = installing
appDownloadFile.isVisible = !installing
appDownloadCancel.isEnabled = !installing
appDownloadCancel.setOnClickListener {
if (installing) {
return@setOnClickListener
}
progressModel.currentDownload?.cancel()
progressModel.downloadProgress.value = 0
dismiss()
}
}
progressModel.downloadingFile.observe(viewLifecycleOwner) {
appDownloadFile.text = it
}
currentDownload?.cancel()
downloadProgress.value = 0
dismiss()
}
}
downloadingFile.observe(viewLifecycleOwner) {
appDownloadFile.text = it
}
}
override fun onResume() {

View File

@ -2,10 +2,11 @@ package com.vanced.manager.ui.dialogs
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.toBitmap
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppInfoBinding
@ -20,15 +21,13 @@ class AppInfoDialog : BindingDialogFragment<DialogAppInfoBinding>() {
fun newInstance(
appName: String?,
@DrawableRes appIcon: Int?,
appIcon: Drawable?,
changelog: String?
): AppInfoDialog = AppInfoDialog().apply {
arguments = Bundle().apply {
putString(TAG_APP_NAME, appName)
putString(TAG_CHANGELOG, changelog)
if (appIcon != null) {
putInt(TAG_APP_ICON, appIcon)
}
putParcelable(TAG_APP_ICON, appIcon?.toBitmap())
}
}
}
@ -48,7 +47,7 @@ class AppInfoDialog : BindingDialogFragment<DialogAppInfoBinding>() {
with(binding) {
aboutAppName.text = getString(R.string.about_app, arguments?.getString(TAG_APP_NAME))
aboutAppChangelog.text = arguments?.getString(TAG_CHANGELOG)
arguments?.getInt(TAG_APP_ICON)?.let { aboutAppImage.setImageResource(it) }
aboutAppImage.setImageBitmap(arguments?.getParcelable(TAG_APP_ICON))
}
}
}

View File

@ -1,62 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppUninstallBinding
import com.vanced.manager.utils.PackageHelper
class AppUninstallDialog : BindingDialogFragment<DialogAppUninstallBinding>() {
companion object {
private const val TAG_APP_NAME = "APP_NAME"
private const val TAG_APP_PACKAGE = "APP_PACKAGE"
fun newInstance(
appName: String?,
appPackage: String?,
) = AppUninstallDialog().apply {
arguments = Bundle().apply {
putString(TAG_APP_NAME, appName)
putString(TAG_APP_PACKAGE, appPackage)
}
}
}
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogAppUninstallBinding.inflate(inflater, container, false)
override fun otherSetups() {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
}
private fun bindData() {
val appName = arguments?.getString(TAG_APP_NAME)
val appPackage = arguments?.getString(TAG_APP_PACKAGE)
with(binding) {
appUninstallConfirm.setOnClickListener {
if (appPackage != null) {
PackageHelper.uninstallApk(
pkg = appPackage,
context = requireActivity()
)
}
dismiss()
}
appUninstallCancel.setOnClickListener {
dismiss()
}
appUninstallMessage.text = getString(R.string.uninstall_app_text, appName)
}
}
}

View File

@ -11,12 +11,10 @@ import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogBottomRadioButtonBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.formatVersion
import com.vanced.manager.utils.getCheckedButtonTag
class AppVersionSelectorDialog :
BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
class AppVersionSelectorDialog : BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
private val prefs by lazy { requireActivity().defPrefs }
@ -65,7 +63,7 @@ class AppVersionSelectorDialog :
}
dialogTitle.text = getString(R.string.version)
dialogSave.setOnClickListener {
val checkedTag = dialogRadiogroup.checkedButtonTag
val checkedTag = dialogRadiogroup.getCheckedButtonTag()
if (checkedTag != null) {
prefs.edit { putString("${arguments?.getString(TAG_APP)}_version", checkedTag) }
}
@ -77,7 +75,7 @@ class AppVersionSelectorDialog :
private fun loadBoxes() =
arguments?.getStringArrayList(TAG_VERSIONS)?.map { version ->
ThemedMaterialRadioButton(requireActivity()).apply {
text = version.formatVersion(requireActivity())
text = version
tag = version
textSize = 18f
}

View File

@ -5,9 +5,9 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R
import com.vanced.manager.utils.isMiuiOptimizationsEnabled
import com.vanced.manager.utils.applyAccent
import com.vanced.manager.utils.isMiui
import com.vanced.manager.utils.openUrl
import com.vanced.manager.utils.showWithAccent
object DialogContainer {
@ -16,21 +16,26 @@ object DialogContainer {
setTitle(context.resources.getString(R.string.welcome))
setMessage(context.resources.getString(R.string.security_context))
setPositiveButton(context.resources.getString(R.string.close)) { dialog, _ ->
dialog.cancel()
dialog.dismiss()
}
setOnDismissListener {
if (isMiui()) {
applyAccentMiuiDialog(context)
}
}
setOnCancelListener {
if (context.isMiuiOptimizationsEnabled) {
miuiDialog(context)
if (isMiui()) {
applyAccentMiuiDialog(context)
}
}
create()
showWithAccent()
applyAccent()
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit { putBoolean("firstLaunch", false) }
}
fun miuiDialog(context: Context) {
private fun applyAccentMiuiDialog(context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.miui_one_title))
setMessage(context.getString(R.string.miui_one))
@ -44,7 +49,7 @@ object DialogContainer {
}
setCancelable(false)
create()
showWithAccent()
applyAccent()
}
}
@ -54,7 +59,7 @@ object DialogContainer {
setMessage("So this statement is false huh? I'll go with True!")
setPositiveButton("wut?") { dialog, _ -> dialog.dismiss() }
create()
showWithAccent()
applyAccent()
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -68,54 +73,28 @@ object DialogContainer {
when (msg) {
context.getString(R.string.installation_signature) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl(
"https://lmgtfy.com/?q=andnixsh+apk+verification+disable",
R.color.Twitter,
context
)
openUrl("https://lmgtfy.com/?q=andnixsh+apk+verification+disable", R.color.Twitter, context)
}
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
}
context.getString(R.string.installation_miui) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl(
"https://telegra.ph/How-to-install-v15-on-MIUI-02-11",
R.color.Telegram,
context
)
openUrl("https://telegra.ph/How-to-install-v15-on-MIUI-02-11", R.color.Telegram, context)
}
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
}
else -> {
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
}
}
create()
showWithAccent()
applyAccent()
}
}
@ -125,7 +104,7 @@ object DialogContainer {
setMessage(msg)
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
create()
showWithAccent()
applyAccent()
}
}

View File

@ -13,8 +13,7 @@ import com.vanced.manager.databinding.DialogInstallationFilesDetectedBinding
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerVariant
class InstallationFilesDetectedDialog :
BindingBottomSheetDialogFragment<DialogInstallationFilesDetectedBinding>() {
class InstallationFilesDetectedDialog : BindingBottomSheetDialogFragment<DialogInstallationFilesDetectedBinding>() {
companion object {
@ -41,17 +40,15 @@ class InstallationFilesDetectedDialog :
private fun bindData() {
with(binding) {
val app =
arguments?.getString(TAG_APP) ?: throw IllegalArgumentException("app name is null")
val app = arguments?.getString(TAG_APP) ?: throw IllegalArgumentException("app name is null")
installationDetectedTitle.text = getString(R.string.app_install_files_detected, app)
installationDetectedSummary.text =
getString(R.string.app_install_files_detected_summary, app)
installationDetectedSummary.text = getString(R.string.app_install_files_detected_summary, app)
installationDetectedRedownload.setOnClickListener {
dismiss()
when (app) {
getString(R.string.vanced) -> showDialog(VancedPreferencesDialog())
getString(R.string.music) -> showDialog(MusicPreferencesDialog())
else -> showDialog(AppDownloadDialog.newInstance(app))
if (app == getString(R.string.vanced))
showDialog(VancedPreferencesDialog())
else {
showDialog(AppDownloadDialog.newInstance(app))
}
}
installationDetectedInstall.setOnClickListener {

View File

@ -2,10 +2,10 @@ package com.vanced.manager.ui.dialogs
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
@ -16,7 +16,6 @@ import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogManagerAccentColorBinding
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.log
class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorBinding>() {
@ -35,7 +34,6 @@ class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorB
) = DialogManagerAccentColorBinding.inflate(inflater, container, false)
override fun otherSetups() {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
}
@ -50,20 +48,9 @@ class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorB
hexEdittext.apply {
setText(accent.toHex(), TextView.BufferType.EDITABLE)
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (length() == 0) {
setText("#")
setSelection(1)
@ -74,8 +61,7 @@ class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorB
val colorFromEditText = Color.parseColor(text.toString())
accentPicker.setColor(colorFromEditText)
mutableAccentColor.value = colorFromEditText
} catch (e: IllegalArgumentException) {
}
} catch (e: IllegalArgumentException) {}
}
}
@ -107,12 +93,8 @@ class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorB
mutableAccentColor.value = colorFromEditText
prefs.managerAccent = colorFromEditText
} catch (e: IllegalArgumentException) {
log("VMTheme", getString(R.string.failed_accent))
Toast.makeText(
requireActivity(),
getString(R.string.failed_accent),
Toast.LENGTH_SHORT
).show()
Log.d("VMTheme", getString(R.string.failed_accent))
Toast.makeText(requireActivity(), getString(R.string.failed_accent), Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

View File

@ -10,7 +10,7 @@ import com.vanced.manager.BuildConfig.MANAGER_LANGUAGES
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerLanguageBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.getLanguageFormat
import com.vanced.manager.utils.managerLang
@ -43,7 +43,7 @@ class ManagerLanguageDialog : BindingBottomSheetDialogFragment<DialogManagerLang
val language = prefs.managerLang
root.findViewWithTag<ThemedMaterialRadioButton>(language)?.isChecked = true
languageSave.setOnClickListener {
val newPref = binding.languageRadiogroup.checkedButtonTag
val newPref = binding.languageRadiogroup.getCheckedButtonTag()
if (language != newPref) {
prefs.managerLang = newPref
dismiss()

View File

@ -7,7 +7,7 @@ import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.google.android.material.radiobutton.MaterialRadioButton
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerThemeBinding
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.managerTheme
class ManagerThemeDialog : BindingBottomSheetDialogFragment<DialogManagerThemeBinding>() {
@ -36,7 +36,7 @@ class ManagerThemeDialog : BindingBottomSheetDialogFragment<DialogManagerThemeBi
val theme = prefs.managerTheme
root.findViewWithTag<MaterialRadioButton>(theme).isChecked = true
themeSave.setOnClickListener {
val newPref = themeRadiogroup.checkedButtonTag
val newPref = themeRadiogroup.getCheckedButtonTag()
if (theme != newPref) {
prefs.managerTheme = newPref
dismiss()

View File

@ -1,6 +1,5 @@
package com.vanced.manager.ui.dialogs
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -17,9 +16,8 @@ import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogManagerUpdateBinding
import com.vanced.manager.utils.DownloadHelper.downloadManager
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import com.vanced.manager.utils.applyAccent
import com.vanced.manager.utils.currentDownload
import com.vanced.manager.utils.downloadProgress
import com.vanced.manager.utils.manager
class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>() {
@ -58,8 +56,7 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
if (arguments?.getBoolean(TAG_FORCE_UPDATE) == true) {
binding.managerUpdatePatient.text =
requireActivity().getString(R.string.please_be_patient)
binding.managerUpdatePatient.text = requireActivity().getString(R.string.please_be_patient)
downloadManager(requireActivity())
} else {
checkUpdates()
@ -71,20 +68,24 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
isCancelable = false
managerUpdateProgressbar.applyAccent()
managerUpdateCancel.setOnClickListener {
downloadProgress.value = 0
currentDownload?.cancel()
with(downloadProgress.value) {
this?.downloadProgress?.value = 0
this?.currentDownload?.cancel()
}
dismiss()
}
bindDownloadProgress()
}
}
@SuppressLint("SetTextI18n")
private fun DialogManagerUpdateBinding.bindDownloadProgress() {
downloadProgress.observe(viewLifecycleOwner) {
managerUpdateProgressbar.progress = it
managerUpdateProgressbarContainer.isVisible = it != 0
managerUpdateProgress.text = "$it%"
with(downloadProgress) {
observe(viewLifecycleOwner) { progressModel ->
progressModel.downloadProgress.observe(viewLifecycleOwner) {
managerUpdateProgressbar.progress = it
managerUpdateProgressbar.isVisible = it != 0
}
}
}
}
@ -93,19 +94,12 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
registerReceiver()
}
override fun onPause() {
super.onPause()
localBroadcastManager.unregisterReceiver(broadcastReceiver)
}
private fun checkUpdates() {
if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) {
binding.managerUpdatePatient.text =
requireActivity().getString(R.string.please_be_patient)
binding.managerUpdatePatient.text = requireActivity().getString(R.string.please_be_patient)
downloadManager(requireActivity())
} else {
binding.managerUpdatePatient.text =
requireActivity().getString(R.string.update_not_found)
binding.managerUpdatePatient.text = requireActivity().getString(R.string.update_not_found)
}
}

View File

@ -8,7 +8,7 @@ import com.google.android.material.radiobutton.MaterialRadioButton
import com.topjohnwu.superuser.Shell
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerVariantBinding
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.managerVariant
class ManagerVariantDialog : BindingBottomSheetDialogFragment<DialogManagerVariantBinding>() {
@ -37,11 +37,11 @@ class ManagerVariantDialog : BindingBottomSheetDialogFragment<DialogManagerVaria
val variant = prefs.managerVariant
root.findViewWithTag<MaterialRadioButton>(variant).isChecked = true
variantSave.setOnClickListener {
val newPref = variantRadiogroup.checkedButtonTag
val newPref = variantRadiogroup.getCheckedButtonTag()
if (variant != newPref) {
prefs.managerVariant =
if (newPref == "root" && Shell.rootAccess()) {
"root"
"root"
} else {
"nonroot"
}

View File

@ -7,7 +7,9 @@ import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogMusicPreferencesBinding
import com.vanced.manager.utils.*
import com.vanced.manager.utils.convertToAppVersions
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.musicVersions
class MusicPreferencesDialog : BindingBottomSheetDialogFragment<DialogMusicPreferencesBinding>() {
@ -33,13 +35,9 @@ class MusicPreferencesDialog : BindingBottomSheetDialogFragment<DialogMusicPrefe
private fun bindData() {
with(binding) {
val musicVersionsConv = musicVersions.value?.value?.convertToAppVersions()
musicInstallTitle.text =
getString(R.string.app_installation_preferences, getString(R.string.music))
musicVersion.text = getString(
R.string.chosen_version,
prefs.musicVersion?.formatVersion(requireActivity())
)
openVersionSelectorLayout.setOnClickListener {
musicInstallTitle.text = getString(R.string.app_installation_preferences, getString(R.string.music))
musicVersion.text = getString(R.string.chosen_version, prefs.getString("music_version", "latest"))
openVersionSelector.setOnClickListener {
dismiss()
showDialog(
AppVersionSelectorDialog.newInstance(

View File

@ -43,17 +43,12 @@ class SelectAppsDialog : BindingBottomSheetDialogFragment<DialogSelectAppsBindin
}
selectAppsSave.setOnClickListener {
if (ad.apps.all { app -> !app.isChecked }) {
Toast.makeText(
requireActivity(),
R.string.select_at_least_one_app,
Toast.LENGTH_SHORT
).show()
Toast.makeText(requireActivity(), R.string.select_at_least_one_app, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
prefs.edit {
ad.apps.forEach { app ->
putBoolean("enable_${app.tag}", app.isChecked)
putBoolean("${app.tag}_notifs", app.isChecked)
}
}
dismiss()

View File

@ -32,7 +32,7 @@ class ServiceDTimerDialog : BindingDialogFragment<DialogServicedTimerBinding>()
}
private fun bindData() {
with(binding) {
with (binding) {
servicedSlider.value = prefs.getInt("serviced_sleep_timer", 1).toFloat()
servicedCancel.setOnClickListener {
dismiss()
@ -41,26 +41,12 @@ class ServiceDTimerDialog : BindingDialogFragment<DialogServicedTimerBinding>()
try {
arrayOf("vanced", "music").forEach { app ->
if (scriptExists(app)) {
val apkFPath =
"${PackageHelper.apkInstallPath}/${app.capitalize(Locale.ROOT)}/base.apk"
getPackageDir(
requireActivity(),
getPkgNameRoot(app)
)?.let { it1 ->
requireActivity().writeServiceDScript(
apkFPath,
it1,
app
)
}
val apkFPath = "${PackageHelper.apkInstallPath}/${app.capitalize(Locale.ROOT)}/base.apk"
getPackageDir(requireActivity(), getPkgNameRoot(app))?.let { it1 -> requireActivity().writeServiceDScript(apkFPath, it1, app) }
}
}
} catch (e: IOException) {
Toast.makeText(
requireActivity(),
R.string.script_save_failed,
Toast.LENGTH_SHORT
).show()
Toast.makeText(requireActivity(), R.string.script_save_failed, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

View File

@ -45,12 +45,11 @@ class URLChangeDialog : BindingDialogFragment<DialogCustomUrlBinding>() {
TextView.BufferType.EDITABLE
)
urlSave.setOnClickListener {
val finalUrl =
if (urlInput.text?.startsWith("https://") == true || urlInput.text?.startsWith("http://") == true) {
urlInput.text?.removeSuffix("/").toString()
} else {
"https://${urlInput.text}".removeSuffix("/")
}
val finalUrl = if (urlInput.text?.startsWith("https://") == true || urlInput.text?.startsWith("http://") == true) {
urlInput.text?.removeSuffix("/").toString()
} else {
"https://${urlInput.text}".removeSuffix("/")
}
saveUrl(finalUrl)
}
urlReset.setOnClickListener { saveUrl(baseUrl) }

View File

@ -19,8 +19,7 @@ import com.vanced.manager.utils.lang
import com.vanced.manager.utils.vanced
import java.util.*
class VancedLanguageSelectionDialog :
BindingBottomSheetDialogFragment<DialogVancedLanguageSelectionBinding>() {
class VancedLanguageSelectionDialog : BindingBottomSheetDialogFragment<DialogVancedLanguageSelectionBinding>() {
companion object {
@ -53,11 +52,7 @@ class VancedLanguageSelectionDialog :
}
}
if (chosenLangs.isEmpty()) {
Toast.makeText(
requireActivity(),
R.string.select_at_least_one_lang,
Toast.LENGTH_SHORT
).show()
Toast.makeText(requireActivity(), R.string.select_at_least_one_lang, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
prefs.lang = chosenLangs.joinToString()

View File

@ -3,11 +3,14 @@ package com.vanced.manager.ui.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogVancedPreferencesBinding
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.vancedPkg
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
import java.util.*
class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPreferencesBinding>() {
@ -35,27 +38,20 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
private fun bindData() {
with(binding) {
val showLang = mutableListOf<String>()
installPrefs.lang?.split(", ")?.forEach { lang ->
installPrefs.lang?.split(", ")?.toTypedArray()?.forEach { lang ->
val loc = Locale(lang)
showLang.add(loc.getDisplayLanguage(loc).capitalize(Locale.ROOT))
}
val vancedVersionsConv = vancedVersions.value?.value?.convertToAppVersions()
vancedInstallTitle.text =
getString(R.string.app_installation_preferences, getString(R.string.vanced))
vancedTheme.text = getString(
R.string.chosen_theme,
installPrefs.theme?.convertToAppTheme(requireActivity())
)
vancedVersion.text = getString(
R.string.chosen_version,
defPrefs.vancedVersion?.formatVersion(requireActivity())
)
vancedInstallTitle.text = getString(R.string.app_installation_preferences, getString(R.string.vanced))
vancedTheme.text = getString(R.string.chosen_theme, installPrefs.getString("theme", "dark")?.convertToAppTheme(requireActivity()))
vancedVersion.text = getString(R.string.chosen_version, defPrefs.getString("vanced_version", "latest"))
vancedLang.text = getString(R.string.chosen_lang, showLang)
openThemeSelectorLayout.setOnClickListener {
openThemeSelector.setOnClickListener {
dismiss()
showDialog(VancedThemeSelectorDialog())
}
openVersionSelectorLayout.setOnClickListener {
openVersionSelector.setOnClickListener {
dismiss()
showDialog(
AppVersionSelectorDialog.newInstance(
@ -64,7 +60,7 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
)
)
}
openLanguageSelectorLayout.setOnClickListener {
openLanguageSelector.setOnClickListener {
dismiss()
showDialog(VancedLanguageSelectionDialog())
}
@ -72,12 +68,33 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
if (showLang.isEmpty()) {
installPrefs.lang = "en"
}
dismiss()
showDialog(
AppDownloadDialog.newInstance(
app = getString(R.string.vanced)
fun downloadVanced(version: String? = null) {
dismiss()
showDialog(
AppDownloadDialog.newInstance(
app = getString(R.string.vanced),
version = version
)
)
)
}
if (defPrefs.managerVariant == "nonroot" && isMicrogBroken && installPrefs.vancedVersion?.getLatestAppVersion(vancedVersions.value?.value ?: listOf(""))?.take(2)?.toIntOrNull() == 16 && !isPackageInstalled(vancedPkg, requireActivity().packageManager)) {
MaterialAlertDialogBuilder(requireActivity()).apply {
setTitle(R.string.microg_bug)
setMessage(R.string.microg_bug_summary)
setPositiveButton(R.string.auth_dialog_ok) { _, _ ->
downloadVanced("15.43.32")
}
setNeutralButton(R.string.cancel) { _, _ ->
dismiss()
}
create()
}.applyAccent()
return@setOnClickListener
}
downloadVanced()
}
}
}

View File

@ -10,13 +10,12 @@ import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogBottomRadioButtonBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.convertToAppTheme
import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.theme
import com.vanced.manager.utils.vanced
class VancedThemeSelectorDialog :
BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
class VancedThemeSelectorDialog : BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
companion object {
@ -25,12 +24,7 @@ class VancedThemeSelectorDialog :
}
}
private val prefs by lazy {
requireActivity().getSharedPreferences(
"installPrefs",
Context.MODE_PRIVATE
)
}
private val prefs by lazy { requireActivity().getSharedPreferences("installPrefs", Context.MODE_PRIVATE) }
override fun binding(
inflater: LayoutInflater,
@ -57,7 +51,7 @@ class VancedThemeSelectorDialog :
tag.isChecked = true
}
dialogSave.setOnClickListener {
val checkedTag = binding.dialogRadiogroup.checkedButtonTag
val checkedTag = binding.dialogRadiogroup.getCheckedButtonTag()
if (checkedTag != null) {
prefs.theme = checkedTag
}
@ -66,7 +60,7 @@ class VancedThemeSelectorDialog :
}
}
private fun loadButtons() = vanced.value?.array<String>("themes")?.value?.map { theme ->
private fun loadButtons() = vanced.value?.array<String>("themes")?.value?.map {theme ->
ThemedMaterialRadioButton(requireActivity()).apply {
text = theme.convertToAppTheme(requireActivity())
tag = theme

View File

@ -0,0 +1,23 @@
package com.vanced.manager.ui.events
open class Event<out T>(private val content: T) {
private var hasBeenHandled = false
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.edit
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
@ -36,11 +37,11 @@ class AboutFragment : BindingFragment<FragmentAboutBinding>() {
@SuppressLint("ClickableViewAccessibility")
private fun dataBind() {
requireActivity().title = getString(R.string.title_about)
binding.aboutVersionCard.setOnClickListener {
binding.aboutHeader.root.setOnClickListener {
showDialog(
AppInfoDialog.newInstance(
appName = getString(R.string.app_name),
appIcon = R.mipmap.ic_launcher,
appIcon = AppCompatResources.getDrawable(requireActivity(), R.mipmap.ic_launcher),
changelog = manager.value?.string("changelog")
)
)
@ -60,25 +61,17 @@ class AboutFragment : BindingFragment<FragmentAboutBinding>() {
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val devSettings = prefs.getBoolean("devSettings", false)
if (!devSettings) {
Toast.makeText(
requireContext(),
"Dev options unlocked!",
Toast.LENGTH_SHORT
).show()
Toast.makeText(requireContext(), "Dev options unlocked!", Toast.LENGTH_SHORT).show()
prefs.edit { putBoolean("devSettings", true) }
} else
Toast.makeText(
requireContext(),
"Dev options already unlocked",
Toast.LENGTH_SHORT
).show()
Toast.makeText(requireContext(), "Dev options already unlocked", Toast.LENGTH_SHORT).show()
}
return@setOnTouchListener true
}
false
}
binding.aboutGithubButton.setOnClickListener { viewModel.openUrl("https://github.com/YTVanced/VancedInstaller") }
binding.aboutLicenseButton.setOnClickListener { viewModel.openUrl("https://raw.githubusercontent.com/YTVanced/VancedInstaller/dev/LICENSE") }
binding.aboutSources.aboutGithubButton.setOnClickListener { viewModel.openUrl("https://github.com/YTVanced/VancedInstaller") }
binding.aboutSources.aboutLicenseButton.setOnClickListener { viewModel.openUrl("https://raw.githubusercontent.com/YTVanced/VancedInstaller/dev/LICENSE") }
}
}

View File

@ -3,15 +3,22 @@ package com.vanced.manager.ui.fragments
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.vanced.manager.BuildConfig
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentDevSettingsBinding
import com.vanced.manager.ui.WelcomeActivity
import com.vanced.manager.ui.dialogs.ManagerUpdateDialog
import com.vanced.manager.ui.dialogs.URLChangeDialog
import com.vanced.manager.utils.authCrowdin
class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() {
@ -33,6 +40,7 @@ class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() {
bindWelcomeLauncher()
bindForceUpdate()
bindChannelURL()
bindCrowdin()
bindKernelArch()
bindAndroidVersion()
}
@ -64,6 +72,31 @@ class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() {
}
}
private fun FragmentDevSettingsBinding.bindCrowdin() {
if (BuildConfig.ENABLE_CROWDIN_AUTH) {
val isAuthorized = Crowdin.isAuthorized()
crowdinCategory.isVisible = true
crowdinAuth.isVisible = !isAuthorized
screenshotUploading.isVisible = isAuthorized
realTimeUpdates.isVisible = isAuthorized
crowdinAuth.setOnClickListener {
requireActivity().authCrowdin()
@RequiresApi(Build.VERSION_CODES.M)
if (!Settings.canDrawOverlays(requireActivity())) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
("package:" + requireActivity().packageName).toUri()
)
startActivityForResult(intent, 69)
}
Crowdin.authorize(requireActivity())
}
}
}
private fun FragmentDevSettingsBinding.bindKernelArch() {
val supportedAbis: Array<String> = Build.SUPPORTED_ABIS

View File

@ -41,12 +41,7 @@ class GrantRootFragment : BindingFragment<FragmentGrantRootBinding>() {
private fun grantRoot() {
if (Shell.rootAccess()) {
getDefaultSharedPreferences(requireActivity()).edit {
putString(
"vanced_variant",
"root"
)
}
getDefaultSharedPreferences(requireActivity()).edit { putString("vanced_variant", "root") }
navigateToFirstLaunch()
} else {
Toast.makeText(requireActivity(), R.string.root_not_granted, Toast.LENGTH_SHORT).show()

View File

@ -9,36 +9,41 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.ViewGroup
import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.viewModels
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.crowdin.platform.util.inflateWithCrowdin
import com.github.florent37.viewtooltip.ViewTooltip
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent
import com.vanced.manager.BuildConfig.VERSION_CODE
import com.vanced.manager.R
import com.vanced.manager.adapter.ExpandableAppListAdapter
import com.vanced.manager.adapter.AppListAdapter
import com.vanced.manager.adapter.LinkAdapter
import com.vanced.manager.adapter.SponsorAdapter
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.FragmentHomeBinding
import com.vanced.manager.ui.dialogs.AppInfoDialog
import com.vanced.manager.ui.dialogs.DialogContainer.installAlertBuilder
import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.ui.viewmodels.HomeViewModelFactory
import com.vanced.manager.utils.isFetching
import com.vanced.manager.utils.manager
class HomeFragment : BindingFragment<FragmentHomeBinding>() {
open class HomeFragment : BindingFragment<FragmentHomeBinding>() {
companion object {
const val INSTALL_FAILED = "INSTALL_FAILED"
const val REFRESH_HOME = "REFRESH_HOME"
}
private val viewModel: HomeViewModel by viewModels()
private val viewModel: HomeViewModel by viewModels {
HomeViewModelFactory(requireActivity())
}
private val localBroadcastManager by lazy { LocalBroadcastManager.getInstance(requireActivity()) }
private val prefs by lazy { PreferenceManager.getDefaultSharedPreferences(requireActivity()) }
private lateinit var tooltip: ViewTooltip
override fun binding(
inflater: LayoutInflater,
@ -53,27 +58,28 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
private fun bindData() {
requireActivity().title = getString(R.string.title_home)
setHasOptionsMenu(true)
with(binding) {
with (binding) {
homeRefresh.setOnRefreshListener { viewModel.fetchData() }
isFetching.observe(viewLifecycleOwner) { homeRefresh.isRefreshing = it }
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.contains("LastVersionCode")) {
if (prefs.getInt("LastVersionCode", -1) < VERSION_CODE) {
showDialog(
AppInfoDialog.newInstance(
appName = getString(R.string.app_name),
appIcon = R.mipmap.ic_launcher,
changelog = manager.value?.string("changelog")
)
)
prefs.edit().putInt("LastVersionCode", VERSION_CODE).apply()
tooltip = ViewTooltip
.on(recyclerAppList)
.position(ViewTooltip.Position.TOP)
.autoHide(false, 0)
.color(ResourcesCompat.getColor(requireActivity().resources, R.color.Twitter, null))
.withShadow(false)
.corner(25)
.onHide {
prefs.edit { putBoolean("show_changelog_tooltip", false) }
}
} else prefs.edit().putInt("LastVersionCode", VERSION_CODE).apply()
.text(requireActivity().getString(R.string.app_changelog_tooltip))
if (prefs.getBoolean("show_changelog_tooltip", true)) {
tooltip.show()
}
recyclerAppList.apply {
layoutManager = LinearLayoutManager(requireActivity())
adapter = ExpandableAppListAdapter(requireActivity(), viewModel /*, tooltip*/)
adapter = AppListAdapter(requireActivity(), viewModel, viewLifecycleOwner, tooltip)
setHasFixedSize(true)
}
@ -96,12 +102,14 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.toolbar_menu, menu)
inflater.inflateWithCrowdin(R.menu.toolbar_menu, menu, resources)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onPause() {
super.onPause()
localBroadcastManager.unregisterReceiver(broadcastReceiver)
tooltip.close()
}
override fun onResume() {
@ -112,11 +120,7 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
INSTALL_FAILED -> installAlertBuilder(
intent.getStringExtra("errorMsg").toString(),
intent.getStringExtra("fullErrorMsg"),
requireActivity()
)
INSTALL_FAILED -> installAlertBuilder(intent.getStringExtra("errorMsg").toString(), intent.getStringExtra("fullErrorMsg"), requireActivity())
REFRESH_HOME -> viewModel.fetchData()
}
}

View File

@ -1,57 +0,0 @@
package com.vanced.manager.ui.fragments
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentLogBinding
import com.vanced.manager.utils.AppUtils.logs
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.util.*
class LogFragment : BindingFragment<FragmentLogBinding>() {
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = FragmentLogBinding.inflate(inflater, container, false)
override fun otherSetups() {
binding.bindData()
}
private fun FragmentLogBinding.bindData() {
val logs = TextUtils.concat(*logs.toTypedArray())
logText.text = logs
logSave.setOnClickListener {
try {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val hour = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
val second = calendar.get(Calendar.SECOND)
val savePath = requireActivity().getExternalFilesDir("logs")?.path + "/$year$month${day}_$hour$minute$second.log"
val log =
File(savePath)
FileWriter(log).apply {
append(logs)
flush()
close()
}
Toast.makeText(requireActivity(), getString(R.string.logs_saved, savePath), Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
Toast.makeText(requireActivity(), R.string.logs_not_saved, Toast.LENGTH_SHORT)
.show()
}
}
}
}

View File

@ -5,13 +5,13 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.edit
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.LinearLayoutManager
import com.vanced.manager.R
import com.vanced.manager.adapter.SelectAppsAdapter
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentSelectAppsBinding
import com.vanced.manager.ui.WelcomeActivity
class SelectAppsFragment : BindingFragment<FragmentSelectAppsBinding>() {
@ -45,14 +45,13 @@ class SelectAppsFragment : BindingFragment<FragmentSelectAppsBinding>() {
private fun actionOnClickAppsFab() {
if (selectAdapter.apps.all { app -> !app.isChecked }) {
Toast.makeText(requireActivity(), R.string.select_at_least_one_app, Toast.LENGTH_SHORT)
.show()
Toast.makeText(requireActivity(), R.string.select_at_least_one_app, Toast.LENGTH_SHORT).show()
return
}
val prefs = getDefaultSharedPreferences(requireActivity())
selectAdapter.apps.forEach { app ->
prefs.edit { putBoolean("enable_${app.tag}", app.isChecked) }
}
(requireActivity() as WelcomeActivity).navigateTo(2)
findNavController().navigate(SelectAppsFragmentDirections.selectAppsToGrantRoot())
}
}

View File

@ -7,7 +7,6 @@ import android.view.MenuInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.firebase.analytics.FirebaseAnalytics
@ -35,7 +34,6 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) }
private lateinit var variant: String
private lateinit var parentActivity: FragmentActivity
override fun binding(
inflater: LayoutInflater,
@ -45,7 +43,6 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
override fun otherSetups() {
setHasOptionsMenu(true)
parentActivity = requireActivity()
bindData()
}
@ -66,8 +63,8 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private fun FragmentSettingsBinding.bindRecycler() {
notificationsRecycler.apply {
layoutManager = LinearLayoutManager(parentActivity)
adapter = GetNotifAdapter(parentActivity)
layoutManager = LinearLayoutManager(requireActivity())
adapter = GetNotifAdapter(requireActivity())
}
}
@ -75,7 +72,7 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
firebase.setOnCheckedListener { _, isChecked ->
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isChecked)
FirebasePerformance.getInstance().isPerformanceCollectionEnabled = isChecked
FirebaseAnalytics.getInstance(parentActivity).setAnalyticsCollectionEnabled(isChecked)
FirebaseAnalytics.getInstance(requireActivity()).setAnalyticsCollectionEnabled(isChecked)
}
}
@ -96,13 +93,7 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private fun FragmentSettingsBinding.bindClearFiles() {
clearFiles.setOnClickListener {
with(requireActivity()) {
listOf(
"vanced/nonroot",
"vanced/root",
"music/nonroot",
"music/root",
"microg"
).forEach { dir ->
listOf("vanced/nonroot", "vanced/root", "music/nonroot", "music/root", "microg").forEach { dir ->
File(getExternalFilesDir(dir)?.path.toString()).deleteRecursively()
}
Toast.makeText(this, getString(R.string.cleared_files), Toast.LENGTH_SHORT).show()
@ -125,7 +116,7 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
}
private fun FragmentSettingsBinding.bindManagerAccentColor() {
managerAccentColor.apply {
managerAccentColor.apply{
setSummary(prefs.getInt("manager_accent_color", defAccentColor).toHex())
setOnClickListener { showDialog(ManagerAccentColorDialog()) }
accentColor.observe(viewLifecycleOwner) {
@ -137,15 +128,14 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private fun FragmentSettingsBinding.bindManagerLanguage() {
val langPref = prefs.getString("manager_lang", "System Default")
managerLanguage.apply {
setSummary(getLanguageFormat(parentActivity, requireNotNull(langPref)))
setSummary(getLanguageFormat(requireActivity(), requireNotNull(langPref)))
setOnClickListener { showDialog(ManagerLanguageDialog()) }
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val devSettings =
getDefaultSharedPreferences(requireActivity()).getBoolean("devSettings", false)
val devSettings = getDefaultSharedPreferences(requireActivity()).getBoolean("devSettings", false)
if (devSettings) {
inflater.inflate(R.menu.dev_settings_menu, menu)
}

View File

@ -1,56 +0,0 @@
package com.vanced.manager.ui.fragments
//import android.os.Bundle
//import android.view.LayoutInflater
//import android.view.View
//import android.view.ViewGroup
//import androidx.compose.foundation.lazy.LazyColumn
//import androidx.compose.ui.platform.ComposeView
//import androidx.fragment.app.Fragment
//import com.vanced.manager.R
//import com.vanced.manager.ui.compose.Preference
//import com.vanced.manager.ui.compose.PreferenceCategory
//import com.vanced.manager.ui.compose.SwitchPreference
//
//class SettingsFragmentCompose : Fragment() {
//
// override fun onCreateView(
// inflater: LayoutInflater,
// container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View {
// return ComposeView(requireActivity()).apply {
// setContent {
// LazyColumn {
// // use `item` for separate elements like headers
// // and `items` for lists of identical elements
// item {
// PreferenceCategory(
// categoryTitle = getString(R.string.category_behaviour)
// ) {
// SwitchPreference(
// preferenceTitle = getString(R.string.use_custom_tabs),
// preferenceDescription = getString(R.string.link_custom_tabs),
// preferenceKey = "use_custom_tabs"
// )
// }
// }
// item {
// PreferenceCategory(
// categoryTitle = getString(R.string.category_appearance)
// ) {
// Preference(
// preferenceTitle = "test",
// preferenceDescription = "test",
// ) {}
// Preference(
// preferenceTitle = "test"
// ) {}
// }
// }
// }
// }
// }
// }
//
//}

View File

@ -3,9 +3,9 @@ package com.vanced.manager.ui.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentWelcomeBinding
import com.vanced.manager.ui.WelcomeActivity
class WelcomeFragment : BindingFragment<FragmentWelcomeBinding>() {
@ -20,8 +20,10 @@ class WelcomeFragment : BindingFragment<FragmentWelcomeBinding>() {
}
private fun bindData() {
binding.welcomeGetStarted.setOnClickListener {
(requireActivity() as WelcomeActivity).navigateTo(1)
}
binding.welcomeGetStarted.setOnClickListener { navigateToWelcome() }
}
private fun navigateToWelcome() {
findNavController().navigate(WelcomeFragmentDirections.welcomeToSelectApps())
}
}

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.AndroidViewModel
import com.vanced.manager.R
import com.vanced.manager.utils.openUrl
class AboutViewModel(application: Application) : AndroidViewModel(application) {
class AboutViewModel(application: Application): AndroidViewModel(application) {
fun openUrl(url: String) {
openUrl(url, R.color.GitHub, getApplication())

View File

@ -1,23 +1,25 @@
package com.vanced.manager.ui.viewmodels
import android.app.Application
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.AndroidViewModel
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.google.android.material.button.MaterialButton
import com.vanced.manager.R
import com.vanced.manager.adapter.LinkAdapter.Companion.DISCORD
import com.vanced.manager.adapter.LinkAdapter.Companion.REDDIT
import com.vanced.manager.adapter.LinkAdapter.Companion.TELEGRAM
import com.vanced.manager.adapter.LinkAdapter.Companion.TWITTER
import com.vanced.manager.adapter.SponsorAdapter.Companion.BRAVE
import com.vanced.manager.model.ButtonTag
import com.vanced.manager.model.DataModel
import com.vanced.manager.model.RootDataModel
import com.vanced.manager.ui.dialogs.AppDownloadDialog
@ -25,7 +27,6 @@ import com.vanced.manager.ui.dialogs.InstallationFilesDetectedDialog
import com.vanced.manager.ui.dialogs.MusicPreferencesDialog
import com.vanced.manager.ui.dialogs.VancedPreferencesDialog
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.AppUtils.managerPkg
import com.vanced.manager.utils.AppUtils.microgPkg
import com.vanced.manager.utils.AppUtils.musicPkg
@ -39,11 +40,10 @@ import com.vanced.manager.utils.PackageHelper.uninstallRootApk
import com.vanced.manager.utils.PackageHelper.vancedInstallFilesExist
import kotlinx.coroutines.launch
class HomeViewModel(application: Application) : AndroidViewModel(application) {
open class HomeViewModel(private val activity: FragmentActivity): ViewModel() {
private val prefs = getDefaultSharedPreferences(context)
private val prefs = getDefaultSharedPreferences(activity)
private val variant get() = prefs.getString("vanced_variant", "nonroot")
private val context: Context get() = getApplication()
val vancedModel = MutableLiveData<DataModel>()
val vancedRootModel = MutableLiveData<RootDataModel>()
@ -54,13 +54,14 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
fun fetchData() {
viewModelScope.launch {
loadJson(context)
loadJson(activity)
Crowdin.forceUpdate(activity)
}
}
private val microgToast = Toast.makeText(activity, R.string.no_microg, Toast.LENGTH_LONG)
private val microgToast = Toast.makeText(context, R.string.no_microg, Toast.LENGTH_LONG)
fun openUrl(context: Context, url: String) {
fun openUrl(url: String) {
val color: Int =
when (url) {
DISCORD -> R.color.Discord
@ -70,83 +71,75 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
BRAVE -> R.color.Brave
else -> R.color.Vanced
}
openUrl(url, color, context)
openUrl(url, color, activity)
}
fun launchApp(app: String, isRoot: Boolean) {
val componentName = when (app) {
context.getString(R.string.vanced) -> if (isRoot) ComponentName(
vancedRootPkg,
"$vancedRootPkg.HomeActivity"
) else ComponentName(vancedPkg, "$vancedRootPkg.HomeActivity")
context.getString(R.string.music) -> if (isRoot) ComponentName(
musicRootPkg,
"$musicRootPkg.activities.MusicActivity"
) else ComponentName(musicPkg, "$musicRootPkg.activities.MusicActivity")
context.getString(R.string.microg) -> ComponentName(
microgPkg,
"org.microg.gms.ui.SettingsActivity"
)
activity.getString(R.string.vanced) -> if (isRoot) ComponentName(vancedRootPkg, "$vancedRootPkg.HomeActivity") else ComponentName(vancedPkg, "$vancedRootPkg.HomeActivity")
activity.getString(R.string.music) -> if (isRoot) ComponentName(musicRootPkg, "$musicRootPkg.activities.MusicActivity") else ComponentName(musicPkg, "$musicRootPkg.activities.MusicActivity")
activity.getString(R.string.microg) -> ComponentName(microgPkg, "org.microg.gms.ui.SettingsActivity")
else -> throw IllegalArgumentException("Can't open this app")
}
try {
context.startActivity(Intent().setComponent(componentName))
activity.startActivity(Intent().setComponent(componentName))
} catch (e: ActivityNotFoundException) {
log("VMHMV", e.toString())
Log.d("VMHMV", e.toString())
}
}
fun openInstallDialog(fragmentManager: FragmentManager, buttonTag: ButtonTag?, app: String) {
if (variant == "nonroot" && app != context.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) {
fun openInstallDialog(view: View, app: String) {
if (variant == "nonroot" && app != activity.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) {
microgToast.show()
return
}
if (buttonTag == ButtonTag.UPDATE) {
if ((view as MaterialButton).text == activity.getString(R.string.update)) {
when (app) {
context.getString(R.string.vanced) -> VancedPreferencesDialog().show(fragmentManager)
context.getString(R.string.music) -> MusicPreferencesDialog().show(fragmentManager)
else -> AppDownloadDialog.newInstance(app).show(fragmentManager)
activity.getString(R.string.vanced) -> VancedPreferencesDialog().show(activity)
activity.getString(R.string.music) -> MusicPreferencesDialog().show(activity)
else -> AppDownloadDialog.newInstance(app).show(activity)
}
return
}
when (app) {
context.getString(R.string.vanced) -> {
activity.getString(R.string.vanced) -> {
when (variant) {
"nonroot" -> {
if (vancedInstallFilesExist(context)) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
if (vancedInstallFilesExist(activity)) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
VancedPreferencesDialog().show(fragmentManager)
VancedPreferencesDialog().show(activity)
}
}
"root" -> {
VancedPreferencesDialog().show(fragmentManager)
VancedPreferencesDialog().show(activity)
}
}
}
context.getString(R.string.music) -> {
activity.getString(R.string.music) -> {
when (variant) {
"nonroot" -> {
if (musicApkExists(context)) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
if (musicApkExists(activity)) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
MusicPreferencesDialog().show(fragmentManager)
MusicPreferencesDialog().show(activity)
}
}
"root" -> {
MusicPreferencesDialog().show(fragmentManager)
MusicPreferencesDialog().show(activity)
}
}
}
context.getString(R.string.microg) -> {
if (apkExist(context, "microg.apk")) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
activity.getString(R.string.microg) -> {
if (apkExist(activity, "microg.apk")) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
AppDownloadDialog.newInstance(app).show(fragmentManager)
AppDownloadDialog.newInstance(app).show(activity)
}
}
}
@ -155,67 +148,23 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
fun uninstallPackage(pkg: String) {
if (variant == "root" && uninstallRootApk(pkg)) {
viewModelScope.launch { loadJson(context) }
viewModelScope.launch { loadJson(activity) }
} else {
uninstallApk(pkg, context)
uninstallApk(pkg, activity)
}
}
init {
with(context) {
with (activity) {
if (variant == "root") {
vancedRootModel.value = RootDataModel(
vanced,
this,
vancedRootPkg,
this.getString(R.string.vanced),
this.getString(R.string.description_vanced),
R.drawable.ic_vanced,
"vanced"
)
musicRootModel.value = RootDataModel(
music,
this,
musicRootPkg,
this.getString(R.string.music),
this.getString(R.string.description_vanced_music),
R.drawable.ic_music,
"music"
)
vancedRootModel.value = RootDataModel(vanced, this, this, vancedRootPkg, this.getString(R.string.vanced), AppCompatResources.getDrawable(this, R.drawable.ic_vanced), "vanced")
musicRootModel.value = RootDataModel(music, this, this, musicRootPkg, this.getString(R.string.music), AppCompatResources.getDrawable(this, R.drawable.ic_music), "music")
} else {
vancedModel.value = DataModel(
vanced,
this,
vancedPkg,
this.getString(R.string.vanced),
this.getString(R.string.description_vanced),
R.drawable.ic_vanced
)
musicModel.value = DataModel(
music,
this,
musicPkg,
this.getString(R.string.music),
this.getString(R.string.description_vanced_music),
R.drawable.ic_music
)
microgModel.value = DataModel(
microg,
this,
microgPkg,
this.getString(R.string.microg),
this.getString(R.string.description_microg),
R.drawable.ic_microg
)
vancedModel.value = DataModel(vanced, this, this, vancedPkg, this.getString(R.string.vanced), AppCompatResources.getDrawable(this, R.drawable.ic_vanced))
musicModel.value = DataModel(music, this, this, musicPkg, this.getString(R.string.music), AppCompatResources.getDrawable(this, R.drawable.ic_music))
microgModel.value = DataModel(microg, this, this, microgPkg, this.getString(R.string.microg), AppCompatResources.getDrawable(this, R.drawable.ic_microg))
}
managerModel.value = DataModel(
manager,
this,
managerPkg,
this.getString(R.string.app_name),
"Just manager meh",
R.mipmap.ic_launcher
)
managerModel.value = DataModel(manager, this, this, managerPkg, this.getString(R.string.app_name), AppCompatResources.getDrawable(this, R.mipmap.ic_launcher))
}
}
}

View File

@ -0,0 +1,14 @@
package com.vanced.manager.ui.viewmodels
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class HomeViewModelFactory(private val activity: FragmentActivity) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return HomeViewModel(activity) as T
}
}

View File

@ -2,81 +2,65 @@ package com.vanced.manager.utils
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.util.Log
import android.content.pm.PackageInstaller
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.vanced.manager.BuildConfig
import com.vanced.manager.BuildConfig.APPLICATION_ID
import com.vanced.manager.R
import com.vanced.manager.ui.dialogs.AppDownloadDialog
import com.vanced.manager.ui.fragments.HomeFragment
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.security.MessageDigest
import java.util.*
object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
object AppUtils: CoroutineScope by CoroutineScope(Dispatchers.IO) {
const val vancedPkg = "com.vanced.android.youtube"
const val vancedRootPkg = "com.google.android.youtube"
const val musicPkg = "com.vanced.android.apps.youtube.music"
const val musicRootPkg = "com.google.android.apps.youtube.music"
const val microgPkg = "com.mgoogle.android.gms"
const val faqpkg = "com.vanced.faq"
const val managerPkg = APPLICATION_ID
const val playStorePkg = "com.android.vending"
val logs = mutableListOf<Spannable>()
var currentLocale: Locale? = null
fun log(tag: String, message: String) {
logs.add(
SpannableString("$tag: $message\n").apply {
setSpan(ForegroundColorSpan(Color.parseColor("#2e73ff")), 0, tag.length + 1, 0)
setSpan(StyleSpan(Typeface.BOLD), 0, tag.length + 1, 0)
setSpan(
ForegroundColorSpan(Color.MAGENTA),
tag.length + 2,
tag.length + message.length + 2,
0
)
}
)
if (BuildConfig.DEBUG) {
Log.d(tag, message)
}
}
fun sendRefresh(context: Context): Job {
return launch {
delay(700)
LocalBroadcastManager.getInstance(context)
.sendBroadcast(Intent(HomeFragment.REFRESH_HOME))
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(HomeFragment.REFRESH_HOME))
}
}
fun sendCloseDialog(context: Context): Job {
return launch {
delay(700)
installing.postValue(false)
LocalBroadcastManager.getInstance(context)
.sendBroadcast(Intent(AppDownloadDialog.CLOSE_DIALOG))
downloadProgress.value?.installing?.postValue(false)
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(AppDownloadDialog.CLOSE_DIALOG))
}
}
fun sendFailure(error: List<String>, context: Context) {
sendFailure(error.joinToString(" "), context)
fun sendFailure(status: Int, fullError: String?, context: Context): Job {
//Delay error broadcast until activity (and fragment) get back to the screen
return launch {
delay(700)
val intent = Intent(HomeFragment.INSTALL_FAILED)
intent.putExtra("errorMsg", getErrorMessage(status, context))
intent.putExtra("fullErrorMsg", fullError)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
}
fun sendFailure(error: MutableList<String>, context: Context): Job {
return launch {
delay(700)
val intent = Intent(HomeFragment.INSTALL_FAILED)
intent.putExtra("errorMsg", getErrorMessage(error.joinToString(), context))
intent.putExtra("fullErrorMsg", error.joinToString(" "))
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
}
fun sendFailure(error: String, context: Context): Job {
//Delay error broadcast until activity (and fragment) get back to the screen
return launch {
delay(700)
val intent = Intent(HomeFragment.INSTALL_FAILED)
@ -102,7 +86,7 @@ object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
private fun printableHexString(data: ByteArray): String {
// Create Hex String
val hexString: StringBuilder = StringBuilder()
for (aMessageDigest: Byte in data) {
for (aMessageDigest:Byte in data) {
var h: String = Integer.toHexString(0xFF and aMessageDigest.toInt())
while (h.length < 2)
h = "0$h"
@ -122,7 +106,6 @@ object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
}
private fun getErrorMessage(status: String, context: Context): String {
log("VMInstall", status)
return when {
status.contains("INSTALL_FAILED_ABORTED") -> context.getString(R.string.installation_aborted)
status.contains("INSTALL_FAILED_ALREADY_EXISTS") -> context.getString(R.string.installation_conflict)
@ -137,10 +120,27 @@ object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
status.contains("ModApk_Missing") -> context.getString(R.string.modapk_missing)
status.contains("Files_Missing_VA") -> context.getString(R.string.files_missing_va)
status.contains("Path_Missing") -> context.getString(R.string.path_missing)
status.contains("INSTALL_FAILED_INTERNAL_ERROR: Permission Denied") -> context.getString(
R.string.installation_miui
)
else -> context.getString(R.string.installation_failed)
else ->
if (isMiui())
context.getString(R.string.installation_miui)
else
context.getString(R.string.installation_failed)
}
}
private fun getErrorMessage(status: Int, context: Context): String {
return when (status) {
PackageInstaller.STATUS_FAILURE_ABORTED -> context.getString(R.string.installation_aborted)
PackageInstaller.STATUS_FAILURE_BLOCKED -> context.getString(R.string.installation_blocked)
PackageInstaller.STATUS_FAILURE_CONFLICT -> context.getString(R.string.installation_conflict)
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> context.getString(R.string.installation_incompatible)
PackageInstaller.STATUS_FAILURE_INVALID -> context.getString(R.string.installation_invalid)
PackageInstaller.STATUS_FAILURE_STORAGE -> context.getString(R.string.installation_storage)
else ->
if (isMiui())
context.getString(R.string.installation_miui)
else
context.getString(R.string.installation_failed)
}
}
}

View File

@ -1,16 +1,9 @@
package com.vanced.manager.utils
import android.app.PendingIntent
import android.os.Build
fun getArch(): String = when {
Build.SUPPORTED_ABIS.contains("x86") -> "x86"
Build.SUPPORTED_ABIS.contains("arm64-v8a") -> "arm64_v8a"
else -> "armeabi_v7a"
}
val intentFlags =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE
else
0
}

View File

@ -1,14 +1,15 @@
package com.vanced.manager.utils
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.core.content.FileProvider
import androidx.lifecycle.MutableLiveData
import com.vanced.manager.R
import com.vanced.manager.library.network.providers.createService
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.model.ProgressModel
import com.vanced.manager.utils.AppUtils.sendCloseDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -38,49 +39,40 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fileFolder: String,
fileName: String,
context: Context,
onDownloadComplete: () -> Unit = {},
onError: (error: String) -> Unit = {}
onDownloadComplete: () -> Unit,
onError: (error: String) -> Unit
) {
downloadingFile.postValue(context.getString(R.string.downloading_file, fileName))
downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.downloading_file, fileName))
val downloadInterface = createService(DownloadHelper::class, baseUrl)
val download = downloadInterface.download(url)
currentDownload = download
downloadProgress.value?.currentDownload = download
download.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
launch {
if (response.body()?.let {
writeFile(
it,
context.getExternalFilesDir(fileFolder)?.path + "/" + fileName
)
} == true) {
CoroutineScope(Dispatchers.IO).launch {
if (response.body()?.let { writeFile(it, context.getExternalFilesDir(fileFolder)?.path + "/" + fileName) } == true) {
onDownloadComplete()
} else {
onError("Could not save file")
downloadProgress.postValue(0)
log(
"VMDownloader",
"Failed to save file: $url\n${response.errorBody()}"
)
downloadProgress.value?.downloadProgress?.postValue(0)
Log.d("VMDownloader", "Failed to save file: $url")
}
}
} else {
val errorBody = response.errorBody().toString()
onError(errorBody)
downloadProgress.postValue(0)
log("VMDownloader", "Failed to download file: $url\n$errorBody")
onError(response.errorBody().toString())
downloadProgress.value?.downloadProgress?.postValue(0)
Log.d("VMDownloader", "Failed to download file: $url")
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
if (call.isCanceled) {
log("VMDownloader", "Download canceled")
downloadProgress.postValue(0)
Log.d("VMDownloader", "Download canceled")
downloadProgress.value?.downloadProgress?.postValue(0)
} else {
onError(t.stackTraceToString())
downloadProgress.postValue(0)
log("VMDownloader", "Failed to download file: $url")
downloadProgress.value?.downloadProgress?.postValue(0)
Log.d("VMDownloader", "Failed to download file: $url")
}
}
@ -102,7 +94,7 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
while (inputStream.read(fileReader).also { read = it } != -1) {
outputStream.write(fileReader, 0, read)
downloadedBytes += read.toLong()
downloadProgress.postValue((downloadedBytes * 100 / totalBytes).toInt())
downloadProgress.value?.downloadProgress?.postValue((downloadedBytes * 100 / totalBytes).toInt())
}
outputStream.flush()
true
@ -117,42 +109,36 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
}
}
val downloadProgress = MutableLiveData<ProgressModel>()
init {
downloadProgress.value = ProgressModel()
}
fun downloadManager(context: Context) {
val url = "https://github.com/YTVanced/VancedManager/releases/latest/download/manager.apk"
download(
url,
"https://github.com/YTVanced/VancedManager/",
"manager",
"manager.apk",
context,
onDownloadComplete = {
val apk = File("${context.getExternalFilesDir("manager")?.path}/manager.apk")
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
FileProvider.getUriForFile(context, "${context.packageName}.provider", apk)
else
Uri.fromFile(apk)
download(url,"https://github.com/YTVanced/VancedManager/", "manager", "manager.apk", context, onDownloadComplete = {
val apk = File("${context.getExternalFilesDir("manager")?.path}/manager.apk")
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
FileProvider.getUriForFile(context, "${context.packageName}.provider", apk)
else
Uri.fromFile(apk)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
log("VMDownloader", e.stackTraceToString())
} finally {
sendCloseDialog(context)
}
},
onError = {
downloadingFile.postValue(
context.getString(
R.string.error_downloading,
"manager.apk"
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
sendCloseDialog(context)
}, onError = {
downloadProgress.value?.downloadingFile?.postValue(
context.getString(
R.string.error_downloading,
"manager.apk"
)
})
)
})
}
}

View File

@ -3,11 +3,11 @@ package com.vanced.manager.utils
import android.content.Context
import android.content.ContextWrapper
import android.content.DialogInterface
import android.util.Log
import android.widget.RadioGroup
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
@ -15,61 +15,55 @@ import com.google.android.material.radiobutton.MaterialRadioButton
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileOutputStream
import com.vanced.manager.R
import com.vanced.manager.utils.AppUtils.log
import java.util.*
val RadioGroup.checkedButtonTag: String?
get() = findViewById<MaterialRadioButton>(
checkedRadioButtonId
)?.tag?.toString()
fun DialogFragment.show(fragmentManager: FragmentManager) {
try {
show(fragmentManager, "")
} catch (e: Exception) {
log("VMUI", e.stackTraceToString())
}
fun RadioGroup.getCheckedButtonTag(): String? {
return findViewById<MaterialRadioButton>(checkedRadioButtonId)?.tag?.toString()
}
fun DialogFragment.show(activity: FragmentActivity) {
show(activity.supportFragmentManager)
try {
show(activity.supportFragmentManager, "")
} catch (e: Exception) {
Log.d("VMUI", e.stackTraceToString())
}
}
fun List<String>.convertToAppVersions(): List<String> = listOf("latest") + reversed()
fun String.formatVersion(context: Context): String =
if (this == "latest") context.getString(R.string.install_latest) else this
fun String.convertToAppTheme(context: Context): String = with(context) {
getString(
R.string.light_plus_other,
if (this@convertToAppTheme == "dark") getString(R.string.vanced_dark) else getString(R.string.vanced_black)
)
fun String.convertToAppTheme(context: Context): String {
return context.getString(R.string.light_plus_other, this.capitalize(Locale.ROOT))
}
fun String.getLatestAppVersion(versions: List<String>): String =
if (this == "latest") versions.reversed()[0] else this
fun String.getLatestAppVersion(versions: List<String>): String = if (this == "latest") versions.reversed()[0] else this
val Context.lifecycleOwner: LifecycleOwner?
get() = when (this) {
is LifecycleOwner -> this
!is LifecycleOwner -> (this as ContextWrapper).baseContext as LifecycleOwner
else -> null
fun Context.lifecycleOwner(): LifecycleOwner? {
var curContext = this
var maxDepth = 20
while (maxDepth-- > 0 && curContext !is LifecycleOwner) {
curContext = (curContext as ContextWrapper).baseContext
}
return if (curContext is LifecycleOwner) {
curContext
} else {
null
}
}
fun Int.toHex(): String = java.lang.String.format("#%06X", 0xFFFFFF and this)
//Material team decided to keep their LinearProgressIndicator final
//At least extension methods exist
fun LinearProgressIndicator.applyAccent() {
with(accentColor.value!!) {
with(accentColor.value ?: context.defPrefs.managerAccent) {
setIndicatorColor(this)
trackColor = ColorUtils.setAlphaComponent(this, 70)
}
}
fun MaterialAlertDialogBuilder.showWithAccent() {
with(accentColor.value!!) {
fun MaterialAlertDialogBuilder.applyAccent() {
with(accentColor.value ?: context.defPrefs.managerAccent) {
show().apply {
getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(this@with)
getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(this@with)
@ -81,12 +75,6 @@ fun MaterialAlertDialogBuilder.showWithAccent() {
fun Context.writeServiceDScript(apkFPath: String, path: String, app: String) {
val shellFileZ = SuFile.open("/data/adb/service.d/$app.sh")
shellFileZ.createNewFile()
val script = """
#!/system/bin/sh
while [ "$(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
sleep ${defPrefs.serviceDSleepTimer}
chcon u:object_r:apk_data_file:s0 $apkFPath
mount -o bind $apkFPath $path
""".trimIndent()
SuFileOutputStream.open(shellFileZ).use { out -> out.write(script.toByteArray()) }
val code = """#!/system/bin/sh${"\n"}while [ "`getprop sys.boot_completed | tr -d '\r' `" != "1" ]; do sleep ${defPrefs.serviceDSleepTimer}; done${"\n"}chcon u:object_r:apk_data_file:s0 $apkFPath${"\n"}mount -o bind $apkFPath $path"""
SuFileOutputStream(shellFileZ).use { out -> out.write(code.toByteArray())}
}

View File

@ -0,0 +1,14 @@
package com.vanced.manager.utils
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
open class FileInfo(val name: String, val fileSize: Long, val file: File? = null) {
open fun getInputStream(): InputStream =
if (file!= null)
FileInputStream(file)
else
throw NotImplementedError("need some way to create InputStream")
}

View File

@ -4,11 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
val Context.installPrefs: SharedPreferences
get() = getSharedPreferences(
"installPrefs",
Context.MODE_PRIVATE
)
val Context.installPrefs: SharedPreferences get() = getSharedPreferences("installPrefs", Context.MODE_PRIVATE)
var SharedPreferences.lang
get() = getString("lang", getDefaultVancedLanguages())

View File

@ -15,7 +15,6 @@ import com.beust.klaxon.JsonArray
import com.beust.klaxon.JsonObject
import com.vanced.manager.R
import com.vanced.manager.utils.AppUtils.generateChecksum
import com.vanced.manager.utils.AppUtils.log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
@ -32,8 +31,8 @@ val music = MutableLiveData<JsonObject?>()
val microg = MutableLiveData<JsonObject?>()
val manager = MutableLiveData<JsonObject?>()
val vancedVersions = MutableLiveData<JsonArray<String>?>()
val musicVersions = MutableLiveData<JsonArray<String>?>()
val vancedVersions = MutableLiveData<JsonArray<String>>()
val musicVersions = MutableLiveData<JsonArray<String>>()
val isFetching = MutableLiveData<Boolean>()
@ -43,28 +42,19 @@ var baseInstallUrl = ""
fun openUrl(url: String, color: Int, context: Context) {
try {
val customTabPrefs =
getDefaultSharedPreferences(context).getBoolean("use_custom_tabs", true)
val customTabPrefs = getDefaultSharedPreferences(context).getBoolean("use_custom_tabs", true)
if (customTabPrefs) {
val builder = CustomTabsIntent.Builder()
val params = CustomTabColorSchemeParams.Builder()
.setToolbarColor(ContextCompat.getColor(context, color))
val params = CustomTabColorSchemeParams.Builder().setToolbarColor(ContextCompat.getColor(context, color))
builder.setDefaultColorSchemeParams(params.build())
val customTabsIntent = builder.build()
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
customTabsIntent.launchUrl(context, url.toUri())
} else
context.startActivity(
Intent(
Intent.ACTION_VIEW,
url.toUri()
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
context.startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show()
} catch (e: SecurityException) {
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show()
}
}
@ -88,17 +78,17 @@ suspend fun loadJson(context: Context) = withContext(Dispatchers.IO) {
connect()
}
if (connection.responseCode != 200) {
log(TAG, latestbaseUrl + ": " + connection.responseCode.toString())
Log.d(TAG, latestbaseUrl + ": " + connection.responseCode.toString())
baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1"
}
} catch (e: IOException) {
baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1"
} catch (e: SocketTimeoutException) {
log(TAG, "connection timed out")
Log.d(TAG, "connection timed out")
baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1"
}
log(TAG, "Fetching using URL: $baseInstallUrl")
Log.d(TAG, "Fetching using URL: $baseInstallUrl")
val calendar = Calendar.getInstance()
val hour = calendar.get(Calendar.HOUR_OF_DAY)
@ -110,7 +100,7 @@ suspend fun loadJson(context: Context) = withContext(Dispatchers.IO) {
val versions = getJson("$baseInstallUrl/versions.json?$fetchTime")
isMicrogBroken = latest?.boolean("is_microg_broken") ?: false
vanced.postValue(latest?.obj("vanced"))
vancedVersions.postValue(versions?.array("vanced"))
vancedVersions.postValue(versions?.array("vanced") )
music.postValue(latest?.obj("music"))
musicVersions.postValue(versions?.array("music"))
microg.postValue(latest?.obj("microg"))
@ -144,4 +134,4 @@ fun checkSHA256(sha256: String, updateFile: File): Boolean {
}
}
const val baseUrl = "https://api.vancedapp.com/api/v1"
const val baseUrl = "https://vancedapp.com/api/v1"

View File

@ -4,7 +4,8 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.res.Configuration
import android.content.res.Resources
import com.vanced.manager.utils.AppUtils.currentLocale
import androidx.preference.PreferenceManager
import com.crowdin.platform.Crowdin
import java.util.*
class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
@ -14,22 +15,20 @@ class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
fun wrap(context: Context): ContextWrapper {
val config: Configuration = context.resources.configuration
context.createConfigurationContext(setLocale(config, context))
Crowdin.wrapContext(context)
return LanguageContextWrapper(context)
}
@Suppress("DEPRECATION")
private fun setLocale(config: Configuration, context: Context): Configuration {
val pref = context.defPrefs.managerLang
val pref = PreferenceManager.getDefaultSharedPreferences(context).getString("manager_lang", "System Default")
val sysLocale = Resources.getSystem().configuration.locale
val locale = when {
pref == "System Default" -> Locale(sysLocale.language, sysLocale.country)
pref?.length!! > 2 -> Locale(
pref.substring(0, pref.length - 3),
pref.substring(pref.length - 2)
)
else -> Locale(pref)
}
currentLocale = locale
val locale =
when {
pref == "System Default" -> Locale(sysLocale.language, sysLocale.country)
pref?.length!! > 2 -> Locale(pref.substring(0, pref.length - 3), pref.substring(pref.length - 2))
else -> Locale(pref)
}
Locale.setDefault(locale)
config.setLocale(locale)
return config

View File

@ -1,10 +1,15 @@
package com.vanced.manager.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.os.LocaleList
import android.provider.Settings
import androidx.annotation.RequiresApi
import com.crowdin.platform.Crowdin
import com.vanced.manager.R
import java.util.*
@ -29,10 +34,7 @@ fun getLanguageFormat(context: Context, language: String): String {
@Suppress("DEPRECATION")
fun getDefaultVancedLanguages(): String {
val serverLangs = vanced.value?.array("langs") ?: mutableListOf("")
val sysLocales =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Resources.getSystem().configuration.locales.toLangTags() else arrayOf(
Resources.getSystem().configuration.locale.language
)
val sysLocales = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Resources.getSystem().configuration.locales.toLangTags() else arrayOf(Resources.getSystem().configuration.locale.language)
val finalLangs = mutableListOf<String>()
sysLocales.forEach { sysLocale ->
when {
@ -51,4 +53,26 @@ fun LocaleList.toLangTags(): Array<String> {
langTags[i] = langTags[i].substring(0, 2)
}
return langTags
}
fun Activity.authCrowdin() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
startActivityForResult(intent, 69)
return
}
Crowdin.authorize(this)
}
}
fun Activity.onActivityResult(requestCode: Int) {
if (requestCode == 69 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
Crowdin.authorize(this)
}
}
}

View File

@ -1,12 +1,22 @@
package com.vanced.manager.utils
import android.content.Context
import android.provider.Settings
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
private const val MIUI_OPTIMIZATION = "miui_optimization"
private const val MIUI_PROP_NAME = "ro.miui.ui.version.name"
val Context.isMiuiOptimizationsEnabled: Boolean
get() = Settings.Secure.getString(
contentResolver,
MIUI_OPTIMIZATION
) == "1"
fun isMiui(): Boolean = !getSystemProps(MIUI_PROP_NAME).isNullOrEmpty()
private fun getSystemProps(propname: String): String? {
var input: BufferedReader? = null
return try {
val process = Runtime.getRuntime().exec("getprop $propname")
input = BufferedReader(InputStreamReader(process.inputStream), 1024)
input.readLine()
} catch (e: IOException) {
null
} finally {
input?.close()
}
}

View File

@ -7,11 +7,12 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.vanced.manager.BuildConfig
import com.vanced.manager.core.installer.AppInstallerService
import com.vanced.manager.core.installer.AppUninstallerService
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.AppUtils.musicRootPkg
import com.vanced.manager.utils.AppUtils.playStorePkg
import com.vanced.manager.utils.AppUtils.sendCloseDialog
@ -21,18 +22,27 @@ import com.vanced.manager.utils.AppUtils.vancedRootPkg
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
object PackageHelper {
const val apkInstallPath = "/data/adb"
private const val INSTALLER_TAG = "VMInstall"
private val vancedThemes =
vanced.value?.array<String>("themes")?.value ?: listOf("black", "dark", "pink", "blue")
private val vancedThemes = vanced.value?.array<String>("themes")?.value ?: listOf("black", "dark", "pink", "blue")
init {
Shell.enableVerboseLogging = BuildConfig.DEBUG
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.setTimeout(10)
)
}
private fun getAppNameRoot(pkg: String): String {
return when (pkg) {
@ -51,6 +61,7 @@ object PackageHelper {
return false
}
fun getPkgNameRoot(app: String): String {
return when (app) {
"vanced" -> vancedRootPkg
@ -58,7 +69,6 @@ object PackageHelper {
else -> ""
}
}
fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean {
return try {
packageManager.getPackageInfo(packageName, 0)
@ -76,7 +86,7 @@ object PackageHelper {
}
@Suppress("DEPRECATION")
fun getPkgVerCode(pkg: String, pm: PackageManager): Int? {
fun getPkgVerCode(pkg: String, pm:PackageManager): Int? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
pm.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt()
else
@ -117,15 +127,9 @@ object PackageHelper {
if (files?.isNotEmpty() == true) {
for (file in files) {
when {
vancedThemes.any { file.name == "$it.apk" } && !splitFiles.contains("base") -> splitFiles.add(
"base"
)
file.name.matches(Regex("split_config\\.(..)\\.apk")) && !splitFiles.contains(
"lang"
) -> splitFiles.add("lang")
(file.name.startsWith("split_config.arm") || file.name.startsWith("split_config.x86")) && !splitFiles.contains(
"arch"
) -> splitFiles.add("arch")
vancedThemes.any { file.name == "$it.apk" } && !splitFiles.contains("base") -> splitFiles.add("base")
file.name.matches(Regex("split_config\\.(..)\\.apk")) && !splitFiles.contains("lang") -> splitFiles.add("lang")
(file.name.startsWith("split_config.arm") || file.name.startsWith("split_config.x86")) && !splitFiles.contains("arch") -> splitFiles.add("arch")
}
if (splitFiles.size == 3) {
@ -149,7 +153,7 @@ object PackageHelper {
fun uninstallApk(pkg: String, context: Context) {
val callbackIntent = Intent(context, AppUninstallerService::class.java)
callbackIntent.putExtra("pkg", pkg)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, intentFlags)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, 0)
try {
context.packageManager.packageInstaller.uninstall(pkg, pendingIntent.intentSender)
} catch (e: Exception) {
@ -158,94 +162,73 @@ object PackageHelper {
}
fun install(path: String, context: Context) {
val callbackIntent = Intent(context, AppInstallerService::class.java)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, intentFlags)
val packageInstaller = context.packageManager.packageInstaller
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
val sessionId: Int
var session: PackageInstaller.Session? = null
try {
sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
val callbackIntent = Intent(context, AppInstallerService::class.java)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, 0)
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
val inputStream: InputStream = FileInputStream(path)
val outputStream = session.openWrite("install", 0, -1)
val buffer = ByteArray(65536)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
var c: Int
while (inputStream.read(buffer).also { c = it } != -1) {
outputStream.write(buffer, 0, c)
}
session.fsync(outputStream)
inputStream.close()
outputStream.close()
session.commit(pendingIntent.intentSender)
} catch (e: Exception) {
log(INSTALLER_TAG, e.stackTraceToString())
sendFailure(e.stackTraceToString(), context)
sendCloseDialog(context)
} finally {
session?.close()
} catch (e: IOException) {
Log.d(INSTALLER_TAG, e.stackTraceToString())
}
}
private fun installRootMusic(files: List<File>, context: Context): Boolean {
private fun installRootMusic(files: ArrayList<FileInfo>, context: Context): Boolean {
files.forEach { apk ->
if (apk.name != "root.apk") {
val newPath = "/data/local/tmp/${apk.name}"
val newPath = "/data/local/tmp/${apk.file?.name}"
//Copy apk to tmp folder in order to avoid permission denials
Shell.su("cp ${apk.path} $newPath").exec()
val command = Shell.su("pm install -r $newPath").exec()
//moving apk to tmp folder in order to avoid permission denials
Shell.su("mv ${apk.file?.path} $newPath").exec()
val command = Shell.su("pm install $newPath").exec()
Shell.su("rm $newPath").exec()
if (!command.isSuccess) {
if (command.isSuccess) {
return true
} else {
sendFailure(command.out, context)
sendCloseDialog(context)
return false
}
}
}
return true
return false
}
private fun installRootApp(
context: Context,
app: String,
appVerCode: Int?,
pkg: String,
modApkBool: (fileName: String) -> Boolean
) = CoroutineScope(Dispatchers.IO).launch {
if (!isMagiskInstalled()) {
sendFailure("NO_MAGISK", context)
sendCloseDialog(context)
return@launch
}
val apkFilesPath = context.getExternalFilesDir("$app/root")?.path
val files = File(apkFilesPath.toString()).listFiles()?.toList()
if (files != null) {
val modApk: File? = files.lastOrNull { modApkBool(it.name) }
if (modApk != null) {
if (appVerCode != null) {
if (overwriteBase(modApk, files, appVerCode, pkg, app, context)) {
private fun installRootApp(context: Context, app: String, appVerCode: Int, pkg: String, modApkBool: (fileName: String) -> Boolean) = CoroutineScope(Dispatchers.IO).launch {
Shell.getShell {
val apkFilesPath = context.getExternalFilesDir("$app/root")?.path
val fileInfoList = apkFilesPath?.let { it1 -> getFileInfoList(it1) }
if (fileInfoList != null) {
val modApk: FileInfo? = fileInfoList.lastOrNull { modApkBool(it.name) }
if (modApk != null) {
if (overwriteBase(modApk, fileInfoList, appVerCode, pkg, app, context)) {
setInstallerPackage(context, pkg, playStorePkg)
log(INSTALLER_TAG, "Finished installation")
Log.d(INSTALLER_TAG, "Finished installation")
sendRefresh(context)
sendCloseDialog(context)
}
} else {
sendFailure("appVerCode is null", context)
sendFailure(listOf("ModApk_Missing").toMutableList(), context)
sendCloseDialog(context)
}
} else {
sendFailure("ModApk_Missing", context)
sendFailure(listOf("Files_Missing_VA").toMutableList(), context)
sendCloseDialog(context)
}
} else {
sendFailure("Files_Missing_VA", context)
sendCloseDialog(context)
}
}
@ -254,7 +237,7 @@ object PackageHelper {
installRootApp(
context,
"music",
music.value?.int("versionCode"),
music.value?.int("versionCode")!!,
musicRootPkg
) {
it == "root.apk"
@ -265,95 +248,202 @@ object PackageHelper {
installRootApp(
context,
"vanced",
vanced.value?.int("versionCode"),
vanced.value?.int("versionCode")!!,
vancedRootPkg
) { fileName ->
vancedThemes.any { fileName == "$it.apk" }
}
}
fun installSplitApkFiles(
context: Context,
appName: String
) {
val packageInstaller = context.packageManager.packageInstaller
val folder = File(context.getExternalFilesDir("$appName/nonroot")?.path.toString())
var session: PackageInstaller.Session? = null
val sessionId: Int
val sessionParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
sessionParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
val callbackIntent = Intent(context, AppInstallerService::class.java)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, intentFlags)
fun installVanced(context: Context): Int {
val apkFolderPath = context.getExternalFilesDir("vanced/nonroot")?.path.toString() + "/"
val nameSizeMap = HashMap<String, Long>()
var totalSize: Long = 0
var sessionId = 0
val folder = File(apkFolderPath)
val listOfFiles = folder.listFiles()
try {
sessionId = packageInstaller.createSession(sessionParams)
session = packageInstaller.openSession(sessionId)
folder.listFiles()?.forEach { apk ->
val inputStream = FileInputStream(apk)
val outputStream = session.openWrite(apk.name, 0, apk.length())
val buffer = ByteArray(65536)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
for (listOfFile in listOfFiles!!) {
if (listOfFile.isFile) {
Log.d(INSTALLER_TAG, "installApk: " + listOfFile.name)
nameSizeMap[listOfFile.name] = listOfFile.length()
totalSize += listOfFile.length()
}
session.fsync(outputStream)
inputStream.close()
outputStream.close()
}
session.commit(pendingIntent.intentSender)
} catch (e: Exception) {
log(INSTALLER_TAG, e.stackTraceToString())
sendFailure(e.stackTraceToString(), context)
sendCloseDialog(context)
e.printStackTrace()
return -1
}
val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
installParams.setSize(totalSize)
try {
sessionId = context.packageManager.packageInstaller.createSession(installParams)
Log.d(INSTALLER_TAG,"Success: created install session [$sessionId]")
for ((key, value) in nameSizeMap) {
doWriteSession(sessionId, apkFolderPath + key, value, key, context)
}
doCommitSession(sessionId, context)
Log.d(INSTALLER_TAG,"Success")
} catch (e: Exception) {
e.printStackTrace()
}
return sessionId
}
private fun doWriteSession(sessionId: Int, inPath: String?, sizeBytes: Long, splitName: String, context: Context): Int {
var inPathToUse = inPath
var sizeBytesToUse = sizeBytes
if ("-" == inPathToUse) {
inPathToUse = null
} else if (inPathToUse != null) {
val file = File(inPathToUse)
if (file.isFile)
sizeBytesToUse = file.length()
}
var session: PackageInstaller.Session? = null
var inputStream: InputStream? = null
var out: OutputStream? = null
try {
session = context.packageManager.packageInstaller.openSession(sessionId)
if (inPathToUse != null) {
inputStream = FileInputStream(inPathToUse)
}
out = session.openWrite(splitName, 0, sizeBytesToUse)
var total = 0
val buffer = ByteArray(65536)
var c: Int
while (true) {
c = inputStream!!.read(buffer)
if (c == -1)
break
total += c
out.write(buffer, 0, c)
}
session.fsync(out)
Log.d(INSTALLER_TAG, "Success: streamed $total bytes")
return PackageInstaller.STATUS_SUCCESS
} catch (e: IOException) {
Log.e(INSTALLER_TAG, "Error: failed to write; " + e.message)
return PackageInstaller.STATUS_FAILURE
} finally {
try {
out?.close()
inputStream?.close()
session?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun doCommitSession(sessionId: Int, context: Context) {
var session: PackageInstaller.Session? = null
try {
session = context.packageManager.packageInstaller.openSession(sessionId)
val callbackIntent = Intent(context, AppInstallerService::class.java)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, 0)
session.commit(pendingIntent.intentSender)
session.close()
Log.d(INSTALLER_TAG, "install request sent")
Log.d(INSTALLER_TAG, "doCommitSession: " + context.packageManager.packageInstaller.mySessions)
Log.d(INSTALLER_TAG, "doCommitSession: after session commit ")
} catch (e: IOException) {
e.printStackTrace()
} finally {
session?.close()
}
}
private fun installSplitApkFilesRoot(apkFiles: List<File>?, context: Context): Boolean {
private fun installSplitApkFiles(apkFiles: ArrayList<FileInfo>, context: Context) : Boolean {
var sessionId: Int?
val filenames = arrayOf("black.apk", "dark.apk", "blue.apk", "pink.apk", "hash.json")
log(INSTALLER_TAG, "installing split apk files: ${apkFiles?.map { it.name }}")
val sessionId =
Shell.su("pm install-create -r").exec().out.joinToString(" ").filter { it.isDigit() }
.toIntOrNull()
if (sessionId == null) {
sendFailure("Session ID is null", context)
sendCloseDialog(context)
return false
Log.d(INSTALLER_TAG, "installing split apk files: $apkFiles")
run {
val sessionIdResult = Shell.su("pm install-create -r -t").exec().out
val sessionIdPattern = Pattern.compile("(\\d+)")
val sessionIdMatcher = sessionIdPattern.matcher(sessionIdResult[0])
sessionIdMatcher.find()
sessionId = Integer.parseInt(sessionIdMatcher.group(1)!!)
}
apkFiles.forEach { apkFile ->
if (!filenames.any { apkFile.name == it }) {
Log.d(INSTALLER_TAG, "installing APK: ${apkFile.name} ${apkFile.fileSize}")
val command = arrayOf("su", "-c", "pm", "install-write", "-S", "${apkFile.fileSize}", "$sessionId", apkFile.name)
val process: Process = Runtime.getRuntime().exec(command)
val inputPipe = apkFile.getInputStream()
try {
process.outputStream.use { outputStream -> inputPipe.copyTo(outputStream) }
} catch (e: Exception) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
process.destroyForcibly()
else
process.destroy()
apkFiles?.filter { !filenames.contains(it.name) }?.forEach { apkFile ->
val apkName = apkFile.name
log(INSTALLER_TAG, "installing APK: $apkName")
val newPath = "/data/local/tmp/$apkName"
//Copy apk to tmp folder in order to avoid permission denials
Shell.su("cp ${apkFile.path} $newPath").exec()
val command = Shell.su("pm install-write $sessionId $apkName $newPath").exec()
Shell.su("rm $newPath").exec()
if (!command.isSuccess) {
sendFailure(command.out, context)
sendCloseDialog(context)
return false
sendFailure(e.stackTrace.map { it.toString() }.toMutableList(), context)
sendCloseDialog(context)
}
process.waitFor()
}
}
log(INSTALLER_TAG, "committing...")
Log.d(INSTALLER_TAG, "committing...")
val installResult = Shell.su("pm install-commit $sessionId").exec()
if (!installResult.isSuccess) {
sendFailure(installResult.out, context)
sendCloseDialog(context)
return false
if (installResult.isSuccess) {
return true
}
return true
sendFailure(installResult.out, context)
sendCloseDialog(context)
return false
}
private fun SimpleDateFormat.tryParse(str: String) = try {
parse(str) != null
} catch (e: Exception) {
false
}
private fun getFileInfoList(splitApkPath: String): ArrayList<FileInfo> {
val parentFile = File(splitApkPath)
val result = ArrayList<FileInfo>()
if (parentFile.exists() && parentFile.canRead()) {
val listFiles = parentFile.listFiles() ?: return ArrayList()
listFiles.mapTo(result) {
FileInfo(it.name, it.length(), it)
}
return result
}
val longLines = Shell.su("ls -l $splitApkPath").exec().out
val pattern = Pattern.compile(" +")
val formatter = SimpleDateFormat("HH:mm", Locale.getDefault())
longLinesLoop@ for (line in longLines) {
val matcher = pattern.matcher(line)
for (i in 0 until 4)
if (!matcher.find())
continue@longLinesLoop
val startSizeStr = matcher.end()
matcher.find()
val endSizeStr = matcher.start()
val fileSizeStr = line.substring(startSizeStr, endSizeStr)
while (true) {
val testTimeStr: String =
line.substring(matcher.end(), line.indexOf(' ', matcher.end()))
if (formatter.tryParse(testTimeStr)) {
//found time, so apk is next
val fileName = line.substring(line.indexOf(' ', matcher.end()) + 1)
if (fileName.endsWith("apk"))
result.add(FileInfo(fileName, fileSizeStr.toLong(), File(splitApkPath, fileName)))
break
}
matcher.find()
}
}
return result
}
//overwrite stock Vanced/Music
private fun overwriteBase(
apkFile: File,
baseApkFiles: List<File>,
apkFile: FileInfo,
baseApkFiles: ArrayList<FileInfo>,
versionCode: Int,
pkg: String,
app: String,
@ -361,15 +451,17 @@ object PackageHelper {
): Boolean {
if (checkVersion(versionCode, baseApkFiles, pkg, context)) {
val path = getPackageDir(context, pkg)
val apath = apkFile.absolutePath
apkFile.file?.let {
val apath = it.absolutePath
setupFolder("$apkInstallPath/${app.capitalize(Locale.ROOT)}")
if (path != null) {
val apkFPath = "$apkInstallPath/${app.capitalize(Locale.ROOT)}/base.apk"
if (moveAPK(apath, apkFPath, pkg, context)) {
if (chConV(apkFPath, context)) {
if (setupScript(apkFPath, path, app, pkg, context)) {
return linkApp(apkFPath, pkg, path)
setupFolder("$apkInstallPath/${app.capitalize(Locale.ROOT)}")
if (path != null) {
val apkFPath = "$apkInstallPath/${app.capitalize(Locale.ROOT)}/base.apk"
if (moveAPK(apath, apkFPath, pkg, context)) {
if (chConV(apkFPath, context)) {
if (setupScript(apkFPath, path, app, pkg, context)) {
return linkApp(apkFPath, pkg, path)
}
}
}
}
@ -378,32 +470,23 @@ object PackageHelper {
return false
}
private fun setupScript(
apkFPath: String,
path: String,
app: String,
pkg: String,
context: Context
): Boolean {
private fun setupScript(apkFPath: String, path: String, app: String, pkg: String, context: Context): Boolean
{
try {
log(INSTALLER_TAG, "Setting up script")
Log.d(INSTALLER_TAG, "Setting up script")
context.writeServiceDScript(apkFPath, path, app)
Shell.su("""echo "#!/system/bin/sh\nwhile read line; do echo \${"$"}{line} | grep $pkg | awk '{print \${'$'}2}' | xargs umount -l; done< /proc/mounts" > /data/adb/post-fs-data.d/$app.sh""")
.exec()
Shell.su("""echo "#!/system/bin/sh\nwhile read line; do echo \${"$"}{line} | grep $pkg | awk '{print \${'$'}2}' | xargs umount -l; done< /proc/mounts" > /data/adb/post-fs-data.d/$app.sh""").exec()
return Shell.su("chmod 744 /data/adb/service.d/$app.sh").exec().isSuccess
} catch (e: IOException) {
sendFailure(e.stackTraceToString(), context)
sendCloseDialog(context)
log(INSTALLER_TAG, e.stackTraceToString())
e.printStackTrace()
}
return false
}
private fun linkApp(apkFPath: String, pkg: String, path: String): Boolean {
log(INSTALLER_TAG, "Linking app")
Log.d(INSTALLER_TAG, "Linking app")
Shell.su("am force-stop $pkg").exec()
Shell.su("""for i in ${'$'}(ls /data/app/ | grep $pkg | tr " "); do umount -l "/data/app/${"$"}i/base.apk"; done """)
.exec()
Shell.su("""for i in ${'$'}(ls /data/app/ | grep $pkg | tr " "); do umount -l "/data/app/${"$"}i/base.apk"; done """).exec()
val response = Shell.su("""su -mm -c "mount -o bind $apkFPath $path"""").exec()
Thread.sleep(500)
Shell.su("am force-stop $pkg").exec()
@ -415,17 +498,12 @@ object PackageHelper {
}
//check version and perform action based on result
private fun checkVersion(
versionCode: Int,
baseApkFiles: List<File>,
pkg: String,
context: Context
): Boolean {
log(INSTALLER_TAG, "Checking stock version")
private fun checkVersion(versionCode: Int, baseApkFiles: ArrayList<FileInfo>, pkg: String, context: Context): Boolean {
Log.d(INSTALLER_TAG, "Checking stock version")
val path = getPackageDir(context, pkg)
if (path != null) {
if (path.contains("/data/app/")) {
when (getVersionNumber(pkg, context)?.let { compareVersion(it, versionCode) }) {
when (getVersionNumber(pkg, context)?.let { compareVersion(it,versionCode) } ) {
1 -> return fixHigherVer(baseApkFiles, pkg, context)
-1 -> return installStock(baseApkFiles, pkg, context)
}
@ -439,8 +517,8 @@ object PackageHelper {
private fun getPkgInfo(pkg: String, context: Context): PackageInfo? {
return try {
context.packageManager.getPackageInfo(pkg, 0)
} catch (e: Exception) {
log(INSTALLER_TAG, "Unable to get package info")
} catch (e:Exception) {
Log.d(INSTALLER_TAG, "Unable to get package info")
null
}
}
@ -454,13 +532,10 @@ object PackageHelper {
}
//uninstall current update and install base that works with patch
private fun fixHigherVer(apkFiles: List<File>, pkg: String, context: Context): Boolean {
log(INSTALLER_TAG, "Downgrading stock")
private fun fixHigherVer(apkFiles: ArrayList<FileInfo>, pkg: String, context: Context) : Boolean {
Log.d(INSTALLER_TAG, "Downgrading stock")
if (uninstallRootApk(pkg)) {
return if (pkg == vancedRootPkg) installSplitApkFilesRoot(
apkFiles,
context
) else installRootMusic(apkFiles, context)
return if (pkg == vancedRootPkg) installSplitApkFiles(apkFiles, context) else installRootMusic(apkFiles, context)
}
sendFailure(listOf("Failed_Uninstall").toMutableList(), context)
sendCloseDialog(context)
@ -468,70 +543,78 @@ object PackageHelper {
}
//install stock youtube matching vanced version
private fun installStock(baseApkFiles: List<File>, pkg: String, context: Context): Boolean {
log(INSTALLER_TAG, "Installing stock")
return if (pkg == vancedRootPkg) installSplitApkFilesRoot(
baseApkFiles,
context
) else installRootMusic(baseApkFiles, context)
private fun installStock(baseApkFiles: ArrayList<FileInfo>, pkg: String, context: Context): Boolean {
Log.d(INSTALLER_TAG, "Installing stock")
return if (pkg == vancedRootPkg) installSplitApkFiles(baseApkFiles, context) else installRootMusic(baseApkFiles, context)
}
private fun isMagiskInstalled() = Shell.su("magisk -c").exec().isSuccess
//set chcon to apk_data_file
private fun chConV(apkFPath: String, context: Context): Boolean {
log(INSTALLER_TAG, "Running chcon")
Log.d(INSTALLER_TAG, "Running chcon")
val response = Shell.su("chcon u:object_r:apk_data_file:s0 $apkFPath").exec()
//val response = Shell.su("chcon -R u:object_r:system_file:s0 $path").exec()
if (!response.isSuccess) {
return if (response.isSuccess) {
true
} else {
sendFailure(response.out, context)
sendCloseDialog(context)
return false
false
}
return true
}
//move patch to data/app
private fun moveAPK(apkFile: String, path: String, pkg: String, context: Context): Boolean {
log(INSTALLER_TAG, "Moving app")
Shell.su("am force-stop $pkg").exec()
private fun moveAPK(apkFile: String, path: String, pkg: String, context: Context) : Boolean {
Log.d(INSTALLER_TAG, "Moving app")
val apkinF = SuFile.open(apkFile)
val apkoutF = SuFile.open(path)
val mv = Shell.su("cp $apkFile $path").exec()
if (!mv.isSuccess) {
sendFailure(mv.out.apply { add(0, "MV_Fail") }, context)
sendCloseDialog(context)
return false
if(apkinF.exists()) {
try {
Shell.su("am force-stop $pkg").exec()
//Shell.su("rm -r SuFile.open(path).parent")
copy(apkinF,apkoutF)
Shell.su("chmod 644 $path").exec().isSuccess
return if(Shell.su("chown system:system $path").exec().isSuccess) {
true
} else {
sendFailure(listOf("Chown_Fail").toMutableList(), context)
sendCloseDialog(context)
false
}
}
catch (e: IOException)
{
sendFailure(listOf("${e.message}").toMutableList(), context)
sendCloseDialog(context)
return false
}
}
sendFailure(listOf("IFile_Missing").toMutableList(), context)
sendCloseDialog(context)
return false
}
val chmod = Shell.su("chmod 644 $path").exec()
if (!chmod.isSuccess) {
sendFailure(chmod.out.apply { add(0, "Chmod_Fail") }, context)
sendCloseDialog(context)
return false
}
val chown = Shell.su("chown system:system $path").exec()
if (!chown.isSuccess) {
sendFailure(chown.out.apply { add(0, "Chown_Fail") }, context)
sendCloseDialog(context)
return false
}
return true
@Throws(IOException::class)
fun copy(src: File, dst: File) {
val cmd = Shell.su("mv ${src.absolutePath} ${dst.absolutePath}").exec().isSuccess
Log.d("ZLog", cmd.toString())
}
@Suppress("DEPRECATION")
private fun getVersionNumber(pkg: String, context: Context): Int? {
try {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
context.packageManager.getPackageInfo(vancedRootPkg, 0).longVersionCode.and(
0xFFFFFFFF
).toInt()
context.packageManager.getPackageInfo(vancedRootPkg, 0).longVersionCode.and(0xFFFFFFFF).toInt()
else
context.packageManager.getPackageInfo(vancedRootPkg, 0).versionCode
} catch (e: Exception) {
}
catch (e : Exception) {
val execRes = Shell.su("dumpsys package $pkg | grep versionCode").exec()
if (execRes.isSuccess) {
if(execRes.isSuccess) {
val result = execRes.out
var version = 0
result
@ -547,16 +630,22 @@ object PackageHelper {
}
//get path of the installed youtube
fun getPackageDir(context: Context, pkg: String): String? {
fun getPackageDir(context: Context, pkg: String): String?
{
val p = getPkgInfo(pkg, context)
return if (p != null) {
return if(p != null)
{
p.applicationInfo.sourceDir
} else {
}
else
{
val execRes = Shell.su("dumpsys package $pkg | grep codePath").exec()
if (execRes.isSuccess) {
if(execRes.isSuccess)
{
val result = execRes.out
for (line in result) {
if (line.contains("data/app")) "${line.substringAfter("=")}/base.apk"
for (line in result)
{
if(line.contains("data/app")) "${line.substringAfter("=")}/base.apk"
}
}
null
@ -566,17 +655,16 @@ object PackageHelper {
private fun setInstallerPackage(context: Context, target: String, installer: String) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
try {
log(INSTALLER_TAG, "Setting installer package to $installer for $target")
Log.d(INSTALLER_TAG, "Setting installer package to $installer for $target")
val installerUid = context.packageManager.getPackageUid(installer, 0)
val res =
Shell.su("""su $installerUid -c 'pm set-installer $target $installer'""").exec()
val res = Shell.su("""su $installerUid -c 'pm set-installer $target $installer'""").exec()
if (res.out.any { line -> line.contains("Success") }) {
log(INSTALLER_TAG, "Installer package successfully set")
Log.d(INSTALLER_TAG, "Installer package successfully set")
return
}
log(INSTALLER_TAG, "Failed setting installer package")
Log.d(INSTALLER_TAG, "Failed setting installer package")
} catch (e: PackageManager.NameNotFoundException) {
log(INSTALLER_TAG, "Installer package $installer not found. Skipping setting installer")
Log.d(INSTALLER_TAG, "Installer package $installer not found. Skipping setting installer")
}
}
}

View File

@ -7,15 +7,15 @@ import androidx.preference.PreferenceManager.getDefaultSharedPreferences
val Context.defPrefs: SharedPreferences get() = getDefaultSharedPreferences(this)
var SharedPreferences.managerTheme
var SharedPreferences.managerTheme
get() = getString("manager_theme", "System Default")
set(value) = edit { putString("manager_theme", value) }
var SharedPreferences.managerAccent
var SharedPreferences.managerAccent
get() = getInt("manager_accent_color", defAccentColor)
set(value) = edit { putInt("manager_accent_color", value) }
var SharedPreferences.managerVariant
var SharedPreferences.managerVariant
get() = getString("vanced_variant", "nonroot")
set(value) = edit { putString("vanced_variant", value) }
@ -23,11 +23,11 @@ var SharedPreferences.managerLang
get() = getString("manager_lang", "System Default")
set(value) = edit { putString("manager_lang", value) }
var SharedPreferences.installUrl
var SharedPreferences.installUrl
get() = getString("install_url", baseUrl)
set(value) = edit { putString("install_url", value) }
var SharedPreferences.vancedVersion
var SharedPreferences.vancedVersion
get() = getString("vanced_version", "latest")
set(value) = edit { putString("vanced_version", value) }

Some files were not shown because too many files have changed in this diff Show More