0
0
Fork 0
mirror of https://github.com/YTVanced/VancedManager synced 2024-11-18 01:05:10 +00:00

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: push:
branches: branches:
- dev - dev
paths-ignore:
- '**.md'
pull_request: pull_request:
branches: branches:
- dev - dev
paths-ignore:
- '**.md'
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: set up JDK 11 - name: set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 1.8
- name: Grant rights - name: Grant rights
run: chmod +x ./gradlew 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 run: ./gradlew assembleDebug
- name: Upload Debug - 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/build/
app/release app/release
local.properties local.properties
/.github/
*.iml *.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 # Vanced Manager
<div> 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.
[![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) 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
</div> ##### Background download/installation feature is no longer supported due to problems with some ROMs, please do NOT report issues regarding background activity.
## Introduction ## Vanced Developers
- xfileFIN
- KevinX8
- Zanezam
- Laura Almeida
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. ## Vanced Manager Developers
- Xinto (X1nto)
- Koopah (ostajic)
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. ## Contributors
- AioiLight
After 3 months of development, we are finally ready to Introduce [Vanced Manager](https://github.com/YTVanced/VancedManager) to the masses!! - HaliksaR
## Features
- 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.
</br>
<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
```
## 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) ![Zanezam](https://i.imgur.com/QVcXA6q.png)
- Laura Almeida - Laura Almeida
![Laura Almeida](https://i.imgur.com/ovVD939.png) ![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.crashlytics")
id("com.google.firebase.firebase-perf") id("com.google.firebase.firebase-perf")
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("kotlin-android")
} }
android { android {
compileSdk = 31 compileSdkVersion(30)
defaultConfig { defaultConfig {
applicationId = "com.vanced.manager" applicationId = "com.vanced.manager"
minSdk = 21 minSdkVersion(21)
targetSdk = 31 targetSdkVersion(30)
versionCode = 262 versionCode = 230
versionName = "2.6.2 (Crimson)" versionName = "2.3.0 (MicroShitMoment)"
vectorDrawables { vectorDrawables.useSupportLibrary = true
useSupportLibrary = true
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")}\"")
} }
buildConfigField("String[]", "MANAGER_LANGUAGES", "{$languages}") lintOptions {
}
lint {
disable("MissingTranslation", "ExtraTranslation") disable("MissingTranslation", "ExtraTranslation")
} }
@ -42,14 +43,13 @@ android {
} }
buildFeatures { buildFeatures {
dataBinding = true // ObservableField migrate to flow or liveData
viewBinding = true viewBinding = true
} }
packagingOptions { packagingOptions {
resources { exclude("META-INF/DEPENDENCIES")
excludes += "META-INF/DEPENDENCIES" exclude("META-INF/*.kotlin_module")
excludes += "META-INF/*.kotlin_module"
}
} }
// To inline the bytecode built with JVM target 1.8 into // 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 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") val exceptions = arrayOf("bn", "pa", "pt", "zh")
File("$projectDir/src/main/res").listFiles()?.filter { File("$projectDir/src/main/res").listFiles()?.forEach { dir ->
val name = it.name if (dir.name.startsWith("values-") && !dir.name.contains("v23")) {
name.startsWith("values-") && !name.contains("v23")
}?.forEach { dir ->
val dirname = dir.name.substringAfter("-").substringBefore("-") val dirname = dir.name.substringAfter("-").substringBefore("-")
if (!exceptions.contains(dirname)) { if (!exceptions.any { dirname == it }) {
langs.add(dirname) langs.add(dirname)
} }
} }
}
return langs.joinToString(", ") { "\"$it\"" } return langs.joinToString(", ") { "\"$it\"" }
} }
dependencies { dependencies {
implementation(project(":core-presentation")) implementation(project(":core-presentation"))
implementation(project(":core-ui")) implementation(project(":core-ui"))
implementation(project(":library-network")) implementation(project(":library-network"))
// Kotlin // Kotlin
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
// AndroidX // AndroidX
implementation("androidx.appcompat:appcompat:1.3.1") implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.browser:browser:1.3.0") implementation("androidx.browser:browser:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.1") implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.3.6") implementation("androidx.fragment:fragment-ktx:1.2.5")
implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1") implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0") implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5") implementation("androidx.navigation:navigation-fragment-ktx:2.3.2")
implementation("androidx.navigation:navigation-ui-ktx:2.3.5") implementation("androidx.navigation:navigation-ui-ktx:2.3.2")
implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 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") //Appearance
implementation("com.google.android.material:material:1.5.0-alpha04") implementation("com.github.madrapps:pikolo:2.0.1")
implementation("com.google.android.material:material:1.3.0-rc01")
// JSON parser // 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 // HTTP networking
implementation("com.github.kittinunf.fuel:fuel:2.3.1") implementation("com.github.kittinunf.fuel:fuel:2.3.0")
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.3.1") implementation("com.github.kittinunf.fuel:fuel-coroutines:2.2.3")
implementation("com.github.kittinunf.fuel:fuel-json:2.3.1") implementation("com.github.kittinunf.fuel:fuel-json:2.2.3")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.2") implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Root permissions // Root permissions
val libsuVersion = "3.1.2" implementation("com.github.topjohnwu.libsu:core:3.0.2")
implementation("com.github.topjohnwu.libsu:core:$libsuVersion") implementation("com.github.topjohnwu.libsu:io:3.0.2")
implementation("com.github.topjohnwu.libsu:io:$libsuVersion")
//implementation("com.github.topjohnwu.libsu:busybox:$libsuVersion")
// Layout // Layout
implementation("com.google.android.flexbox:flexbox:3.0.0") implementation("com.google.android:flexbox:2.0.1")
// Firebase // Firebase
implementation("com.google.firebase:firebase-analytics-ktx:19.0.2") implementation("com.google.firebase:firebase-analytics-ktx:18.0.1")
implementation("com.google.firebase:firebase-crashlytics:18.2.3") implementation("com.google.firebase:firebase-crashlytics:17.3.0")
implementation("com.google.firebase:firebase-messaging:22.0.0") implementation("com.google.firebase:firebase-messaging:21.0.1")
implementation("com.google.firebase:firebase-perf:20.0.3") implementation("com.google.firebase:firebase-perf:19.1.0")
} }

View file

@ -17,7 +17,7 @@
# Uncomment this to preserve the line number information for # Uncomment this to preserve the line number information for
# debugging stack traces. # debugging stack traces.
-keepattributes SourceFile, LineNumberTable #-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # 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.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_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.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- is required for some Android 5.x devices --> <!-- is required for some Android 5.x devices -->
<uses-permission <uses-permission
@ -21,7 +21,6 @@
<package android:name="com.vanced.android.apps.youtube.music" /> <package android:name="com.vanced.android.apps.youtube.music" />
<package android:name="com.google.android.apps.youtube.music" /> <package android:name="com.google.android.apps.youtube.music" />
<package android:name="com.mgoogle.android.gms" /> <package android:name="com.mgoogle.android.gms" />
<package android:name="com.vanced.faq" />
<package android:name="com.android.vending" /> <package android:name="com.android.vending" />
</queries> </queries>
@ -32,13 +31,13 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"> android:supportsRtl="true"
tools:ignore="UnusedAttribute">
<activity <activity
android:name=".ui.SplashScreenActivity" android:name=".ui.core.SplashScreenActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/SplashTheme" android:theme="@style/SplashTheme">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -56,10 +55,10 @@
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:configChanges="layoutDirection|locale|keyboardHidden|orientation|screenSize" android:configChanges="layoutDirection|locale"
android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/DarkTheme" android:theme="@style/DarkTheme">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -67,7 +66,8 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https" android:scheme="https"
android:host="api.vancedapp.com"/> android:host="vancedapp.com"
android:pathPrefix="/downloads"/>
</intent-filter> </intent-filter>
@ -76,8 +76,8 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:grantUriPermissions="true" android:exported="false"
android:exported="false"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" 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.R
import com.vanced.manager.databinding.ViewNotificationSettingBinding import com.vanced.manager.databinding.ViewNotificationSettingBinding
import com.vanced.manager.model.NotifModel import com.vanced.manager.model.NotifModel
import com.vanced.manager.utils.defPrefs
class GetNotifAdapter(private val context: Context) : class GetNotifAdapter(private val context: Context) :
RecyclerView.Adapter<GetNotifAdapter.GetNotifViewHolder>() { RecyclerView.Adapter<GetNotifAdapter.GetNotifViewHolder>() {
private val prefs = context.defPrefs
private val vanced = NotifModel( private val vanced = NotifModel(
"Vanced-Update", "Vanced-Update",
context.getString(R.string.push_notifications, context.getString(R.string.vanced)), 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) private val apps = arrayOf(vanced, music, microg)
inner class GetNotifViewHolder(val binding: ViewNotificationSettingBinding) : inner class GetNotifViewHolder(val binding: ViewNotificationSettingBinding) : RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root) {
val switch = binding.notifSwitch val switch = binding.notifSwitch
fun bind(position: Int) { fun bind(position: Int) {
val app = apps[position]
with(binding.notifSwitch) { with(binding.notifSwitch) {
setKey(app.key) setKey(apps[position].key)
setSummary(app.switchSummary) setSummary(apps[position].switchSummary)
setTitle(app.switchTitle) setTitle(apps[position].switchTitle)
setDefaultValue(true) 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) val links = arrayOf(instagram, youtube, github, website, telegram, twitter, discord, reddit)
inner class LinkViewHolder(private val binding: ViewSocialLinkBinding) : inner class LinkViewHolder(private val binding: ViewSocialLinkBinding) : RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root) {
val logo = binding.linkImage val logo = binding.linkImage
fun bind(position: Int) { fun bind(position: Int) {
binding.linkBg.setOnClickListener { 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( private val vanced = SelectAppModel(
context.getString(R.string.vanced), context.getString(R.string.vanced),
context.getString(R.string.description_vanced), context.getString(R.string.select_apps_vanced),
"vanced", "vanced",
prefs.enableVanced prefs.enableVanced
) )
private val music = SelectAppModel( private val music = SelectAppModel(
context.getString(R.string.music), context.getString(R.string.music),
context.getString(R.string.description_vanced_music), context.getString(R.string.select_apps_music),
"music", "music",
prefs.enableMusic prefs.enableMusic
) )
val apps = arrayOf(vanced, music) val apps = arrayOf(vanced, music)
inner class SelectAppsViewHolder(binding: ViewAppCheckboxBinding) : inner class SelectAppsViewHolder(binding: ViewAppCheckboxBinding) : RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root) {
val appName = binding.appCheckboxText val appName = binding.appCheckboxText
val appDescription = binding.appCheckboxDescription val appDescription = binding.appCheckboxDescription
val appCard = binding.appCheckboxBg 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.databinding.ViewSponsorBinding
import com.vanced.manager.model.SponsorModel import com.vanced.manager.model.SponsorModel
import com.vanced.manager.ui.viewmodels.HomeViewModel import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.utils.LIGHT
import com.vanced.manager.utils.currentTheme
class SponsorAdapter( class SponsorAdapter(
private val context: Context, private val context: Context,
private val viewModel: HomeViewModel private val viewModel: HomeViewModel,
//private val json: ObservableField<JsonObject?>
) : RecyclerView.Adapter<SponsorAdapter.LinkViewHolder>() { ) : RecyclerView.Adapter<SponsorAdapter.LinkViewHolder>() {
private val brave = SponsorModel( private val brave = SponsorModel(
if (currentTheme == LIGHT) AppCompatResources.getDrawable( AppCompatResources.getDrawable(context, R.drawable.ic_brave),
context,
R.drawable.ic_brave_light
) else AppCompatResources.getDrawable(context, R.drawable.ic_brave),
"Brave", "Brave",
BRAVE BRAVE
) )
@ -42,7 +38,7 @@ class SponsorAdapter(
with(binding) { with(binding) {
sponsorName.text = sponsors[position].name sponsorName.text = sponsors[position].name
cardSponsor.setOnClickListener { 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 package com.vanced.manager.core
import android.app.Application import android.app.Application
import android.content.res.Configuration
import android.util.Log
import androidx.preference.PreferenceManager.getDefaultSharedPreferences import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.topjohnwu.superuser.Shell import com.crowdin.platform.Crowdin
import com.vanced.manager.BuildConfig 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.loadJson
import com.vanced.manager.utils.managerAccent
import com.vanced.manager.utils.mutableAccentColor
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class App : Application() { open class App: Application() {
private val prefs by lazy { getDefaultSharedPreferences(this) } private val prefs by lazy { getDefaultSharedPreferences(this) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -20,15 +23,30 @@ class App : Application() {
override fun onCreate() { override fun onCreate() {
scope.launch { loadJson(this@App) } scope.launch { loadJson(this@App) }
super.onCreate() super.onCreate()
mutableAccentColor.value = prefs.managerAccent
Shell.enableVerboseLogging = BuildConfig.DEBUG Crowdin.init(this,
Shell.setDefaultBuilder( CrowdinConfig.Builder().apply {
Shell.Builder withDistributionHash(CROWDIN_HASH)
.create() withNetworkType(NetworkType.WIFI)
.setFlags(Shell.FLAG_REDIRECT_STDERR) if (ENABLE_CROWDIN_AUTH) {
//.setInitializers(BusyBoxInstaller::class.java) //TODO fix busybox if (prefs.getBoolean("crowdin_real_time", false))
.setTimeout(10) 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 android.content.Context
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.utils.*
import com.vanced.manager.utils.DownloadHelper.download 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.PackageHelper.install
import com.vanced.manager.utils.baseInstallUrl
import com.vanced.manager.utils.microg
object MicrogDownloader { object MicrogDownloader {
@ -16,14 +18,14 @@ object MicrogDownloader {
download(url, "$baseInstallUrl/", folderName, fileName, context, onDownloadComplete = { download(url, "$baseInstallUrl/", folderName, fileName, context, onDownloadComplete = {
startMicrogInstall(context) startMicrogInstall(context)
}, onError = { }, 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) { fun startMicrogInstall(context: Context) {
installing.postValue(true) downloadProgress.value?.installing?.postValue(true)
postReset() downloadProgress.value?.postReset()
install("${context.getExternalFilesDir(folderName)}/$fileName", context) 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.musicRootPkg
import com.vanced.manager.utils.AppUtils.validateTheme import com.vanced.manager.utils.AppUtils.validateTheme
import com.vanced.manager.utils.DownloadHelper.download 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.downloadStockCheck
import com.vanced.manager.utils.PackageHelper.install import com.vanced.manager.utils.PackageHelper.install
import com.vanced.manager.utils.PackageHelper.installMusicRoot import com.vanced.manager.utils.PackageHelper.installMusicRoot
@ -13,21 +14,19 @@ import com.vanced.manager.utils.PackageHelper.installMusicRoot
object MusicDownloader { object MusicDownloader {
private var variant: String? = null private var variant: String? = null
private var musicVersion: String? = null private var version: String? = null
private var versionCode: Int? = null private var versionCode: Int? = null
private var baseurl = "" private var baseurl = ""
private var folderName: String? = null private var folderName: String? = null
private var downloadPath: String? = null private var downloadPath: String? = null
private var hashUrl: String? = null private var hashUrl: String? = null
fun downloadMusic(context: Context, version: String? = null) { fun downloadMusic(context: Context) {
val prefs = context.defPrefs val prefs = context.defPrefs
musicVersion = version ?: prefs.musicVersion?.getLatestAppVersion( version = prefs.musicVersion?.getLatestAppVersion(musicVersions.value?.value ?: listOf(""))
musicVersions.value?.value ?: listOf("")
)
versionCode = music.value?.int("versionCode") versionCode = music.value?.int("versionCode")
variant = prefs.managerVariant variant = prefs.managerVariant
baseurl = "$baseInstallUrl/music/v$musicVersion" baseurl = "$baseInstallUrl/music/v$version"
folderName = "music/$variant" folderName = "music/$variant"
downloadPath = context.getExternalFilesDir(folderName)?.path downloadPath = context.getExternalFilesDir(folderName)?.path
hashUrl = "$baseurl/hash.json" hashUrl = "$baseurl/hash.json"
@ -37,13 +36,7 @@ object MusicDownloader {
private fun downloadApk(context: Context, apk: String = "music") { private fun downloadApk(context: Context, apk: String = "music") {
val url = if (apk == "stock") "$baseurl/stock/${getArch()}.apk" else "$baseurl/$variant.apk" val url = if (apk == "stock") "$baseurl/stock/${getArch()}.apk" else "$baseurl/$variant.apk"
download( download(url, "$baseurl/", folderName!!, getFileNameFromUrl(url), context, onDownloadComplete = {
url,
"$baseurl/",
folderName!!,
getFileNameFromUrl(url),
context,
onDownloadComplete = {
if (variant == "root" && apk != "stock") { if (variant == "root" && apk != "stock") {
downloadApk(context, "stock") downloadApk(context, "stock")
return@download return@download
@ -65,20 +58,14 @@ object MusicDownloader {
} }
"stock" -> startMusicInstall(context) "stock" -> startMusicInstall(context)
} }
}, }, onError = {
onError = { downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, getFileNameFromUrl(url)))
downloadingFile.postValue(
context.getString(
R.string.error_downloading,
getFileNameFromUrl(url)
)
)
}) })
} }
fun startMusicInstall(context: Context) { fun startMusicInstall(context: Context) {
installing.postValue(true) downloadProgress.value?.installing?.postValue(true)
postReset() downloadProgress.value?.postReset()
if (variant == "root") if (variant == "root")
installMusicRoot(context) installMusicRoot(context)
else else

View file

@ -2,16 +2,17 @@ package com.vanced.manager.core.downloader
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.logEvent import com.google.firebase.analytics.ktx.logEvent
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.utils.* 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.validateTheme
import com.vanced.manager.utils.AppUtils.vancedRootPkg import com.vanced.manager.utils.AppUtils.vancedRootPkg
import com.vanced.manager.utils.DownloadHelper.download 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.downloadStockCheck
import com.vanced.manager.utils.PackageHelper.installSplitApkFiles import com.vanced.manager.utils.PackageHelper.installVanced
import com.vanced.manager.utils.PackageHelper.installVancedRoot import com.vanced.manager.utils.PackageHelper.installVancedRoot
import java.io.File import java.io.File
@ -47,9 +48,7 @@ object VancedDownloader {
lang = it.split(", ").toMutableList() lang = it.split(", ").toMutableList()
} }
theme = prefs.theme theme = prefs.theme
vancedVersion = version ?: defPrefs.vancedVersion?.getLatestAppVersion( vancedVersion = version ?: defPrefs.vancedVersion?.getLatestAppVersion(vancedVersions.value?.value ?: listOf(""))
vancedVersions.value?.value ?: listOf("")
)
themePath = "$baseInstallUrl/apks/v$vancedVersion/$variant/Theme" themePath = "$baseInstallUrl/apks/v$vancedVersion/$variant/Theme"
hashUrl = "apks/v$vancedVersion/$variant/Theme/hash.json" hashUrl = "apks/v$vancedVersion/$variant/Theme/hash.json"
arch = getArch() arch = getArch()
@ -59,8 +58,8 @@ object VancedDownloader {
try { try {
downloadSplits(context) downloadSplits(context)
} catch (e: Exception) { } catch (e: Exception) {
log("VMDownloader", e.stackTraceToString()) Log.d("VMDownloader", e.stackTraceToString())
downloadingFile.postValue(context.getString(R.string.error_downloading, "Vanced")) downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, "Vanced"))
} }
} }
@ -75,13 +74,7 @@ object VancedDownloader {
else -> throw NotImplementedError("This type of APK is NOT valid. What the hell did you even do?") else -> throw NotImplementedError("This type of APK is NOT valid. What the hell did you even do?")
} }
download( download(url, "$baseInstallUrl/", folderName!!, getFileNameFromUrl(url), context, onDownloadComplete = {
url,
"$baseInstallUrl/",
folderName!!,
getFileNameFromUrl(url),
context,
onDownloadComplete = {
when (type) { when (type) {
"theme" -> "theme" ->
if (variant == "root") { if (variant == "root") {
@ -94,10 +87,7 @@ object VancedDownloader {
downloadSplits(context, "theme") downloadSplits(context, "theme")
} else } else
downloadSplits(context, "arch") downloadSplits(context, "arch")
"arch" -> if (variant == "root") downloadSplits( "arch" -> if (variant == "root") downloadSplits(context, "stock") else downloadSplits(context, "lang")
context,
"stock"
) else downloadSplits(context, "lang")
"stock" -> downloadSplits(context, "dpi") "stock" -> downloadSplits(context, "dpi")
"dpi" -> downloadSplits(context, "lang") "dpi" -> downloadSplits(context, "lang")
"lang" -> { "lang" -> {
@ -110,8 +100,7 @@ object VancedDownloader {
} }
} }
}, }, onError = {
onError = {
if (type == "lang") { if (type == "lang") {
count++ count++
when { when {
@ -124,19 +113,14 @@ object VancedDownloader {
} }
} else { } else {
downloadingFile.postValue( downloadProgress.value?.downloadingFile?.postValue(context.getString(R.string.error_downloading, getFileNameFromUrl(url)))
context.getString(
R.string.error_downloading,
getFileNameFromUrl(url)
)
)
} }
}) })
} }
fun startVancedInstall(context: Context, variant: String? = this.variant) { fun startVancedInstall(context: Context, variant: String? = this.variant) {
installing.postValue(true) downloadProgress.value?.installing?.postValue(true)
postReset() downloadProgress.value?.postReset()
FirebaseAnalytics.getInstance(context).logEvent(FirebaseAnalytics.Event.SELECT_ITEM) { FirebaseAnalytics.getInstance(context).logEvent(FirebaseAnalytics.Event.SELECT_ITEM) {
variant?.let { param("vanced_variant", it) } variant?.let { param("vanced_variant", it) }
theme?.let { param("vanced_theme", it) } theme?.let { param("vanced_theme", it) }
@ -144,6 +128,6 @@ object VancedDownloader {
if (variant == "root") if (variant == "root")
installVancedRoot(context) installVancedRoot(context)
else else
installSplitApkFiles(context, "vanced") installVanced(context)
} }
} }

View file

@ -1,12 +1,13 @@
package com.vanced.manager.core.firebase package com.vanced.manager.core.firebase
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.vanced.manager.utils.AppUtils.log
class CloudMessaging : FirebaseMessagingService() { class CloudMessaging : FirebaseMessagingService() {
override fun onNewToken(p0: String) { 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.Intent
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.os.IBinder 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.sendCloseDialog
import com.vanced.manager.utils.AppUtils.sendFailure import com.vanced.manager.utils.AppUtils.sendFailure
import com.vanced.manager.utils.AppUtils.sendRefresh import com.vanced.manager.utils.AppUtils.sendRefresh
class AppInstallerService : Service() { class AppInstallerService: Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) { when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> { 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) val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try { try {
startActivity(confirmationIntent) startActivity(confirmationIntent)
} catch (e: Exception) { } catch (e: Exception) {
log("VMInstall", "Unable to start installation") Log.d("VMInstall", "Unable to start installation")
} }
} }
PackageInstaller.STATUS_SUCCESS -> { PackageInstaller.STATUS_SUCCESS -> {
log(TAG, "Installation succeed") Log.d(TAG, "Installation succeed")
sendCloseDialog(this) sendCloseDialog(this)
sendRefresh(this) sendRefresh(this)
} }
else -> { else -> {
sendCloseDialog(this) sendCloseDialog(this)
intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)?.let { sendFailure(intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999), intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE),this)
sendFailure(it, this)
}
} }
} }
stopSelf() stopSelf()

View file

@ -4,16 +4,16 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.os.IBinder import android.os.IBinder
import com.vanced.manager.utils.AppUtils.log import android.util.Log
import com.vanced.manager.utils.AppUtils.sendRefresh import com.vanced.manager.utils.AppUtils.sendRefresh
class AppUninstallerService : Service() { class AppUninstallerService: Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val pkgName = intent?.getStringExtra("pkg") val pkgName = intent?.getStringExtra("pkg")
when (intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) { when (intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> { 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) val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) confirmationIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try { try {
@ -24,11 +24,11 @@ class AppUninstallerService : Service() {
//Delay broadcast until activity (and fragment) show up on the screen //Delay broadcast until activity (and fragment) show up on the screen
PackageInstaller.STATUS_SUCCESS -> { PackageInstaller.STATUS_SUCCESS -> {
sendRefresh(this) sendRefresh(this)
log("VMpm", "Successfully uninstalled $pkgName") Log.d("VMpm", "Successfully uninstalled $pkgName")
} }
PackageInstaller.STATUS_FAILURE -> { PackageInstaller.STATUS_FAILURE -> {
sendRefresh(this) sendRefresh(this)
log("VMpm", "Failed to uninstall $pkgName") Log.d("VMpm", "Failed to uninstall $pkgName")
} }
} }
stopSelf() 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.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import androidx.annotation.DrawableRes import androidx.lifecycle.LifecycleOwner
import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData
import androidx.lifecycle.* import androidx.lifecycle.MutableLiveData
import com.beust.klaxon.JsonObject import com.beust.klaxon.JsonObject
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.core.CombinedLiveData
import com.vanced.manager.utils.PackageHelper.isPackageInstalled import com.vanced.manager.utils.PackageHelper.isPackageInstalled
open class DataModel( open class DataModel(
jsonObject: LiveData<JsonObject?>, private val jsonObject: LiveData<JsonObject?>,
context: Context, private val context: Context,
lifecycleOwner: LifecycleOwner,
val appPkg: String, val appPkg: String,
val appName: String, val appName: String,
val appDescription: String, val appIcon: Drawable?,
@DrawableRes val appIcon: Int
) { ) {
val isAppInstalled = Transformations.map(jsonObject) { isAppInstalled(appPkg) } private val versionCode = MutableLiveData<Int>()
private val installedVersionCode = MutableLiveData<Int>()
private val versionCode = Transformations.map(jsonObject) { jobj -> val isAppInstalled = MutableLiveData<Boolean>()
jobj?.int("versionCode") ?: 0 val versionName = MutableLiveData<String>()
} val installedVersionName = MutableLiveData<String>()
private val installedVersionCode = Transformations.map(isAppInstalled) { val buttonTxt = MutableLiveData<String>()
getPkgVersionCode(appPkg, it) val changelog = MutableLiveData<String>()
}
private val unavailable = context.getString(R.string.unavailable)
private val pm = context.packageManager
val versionName = Transformations.map(jsonObject) { jobj -> private fun fetch() {
jobj?.string("version") ?: unavailable val jobj = jsonObject.value
} isAppInstalled.value = isAppInstalled(appPkg)
val changelog = Transformations.map(jsonObject) { jobj -> versionCode.value = jobj?.int("versionCode") ?: 0
jobj?.string("changelog") ?: unavailable versionName.value = jobj?.string("version")?.removeSuffix("-vanced") ?: context.getString(R.string.unavailable)
} changelog.value = jobj?.string("changelog") ?: context.getString(R.string.unavailable)
val installedVersionName = Transformations.map(isAppInstalled) {
getPkgVersionName(appPkg, it)
}
val buttonTag = CombinedLiveData(versionCode, installedVersionCode) { versionCode, installedVersionCode ->
compareInt(installedVersionCode, versionCode)
} }
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 { open fun isAppInstalled(pkg: String): Boolean = isPackageInstalled(pkg, context.packageManager)
return if (isAppInstalled) {
pm?.getPackageInfo(pkg, 0)?.versionName?.removeSuffix("-vanced") ?: unavailable 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 { } else {
unavailable context.getString(R.string.unavailable)
} }
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun getPkgVersionCode(pkg: String, isAppInstalled: Boolean): Int { private fun getPkgVersionCode(pkg: String): Int {
return if (isAppInstalled) { val pm = context.packageManager
return if (isAppInstalled.value == true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
pm?.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt() ?: 0 pm?.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt() ?: 0
else else
pm?.getPackageInfo(pkg, 0)?.versionCode ?: 0 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) { if (int2 != null && int1 != null) {
return when { return when {
int1 == 0 -> ButtonTag.INSTALL int1 == 0 -> context.getString(R.string.install)
int2 > int1 -> ButtonTag.UPDATE int2 > int1 -> context.getString(R.string.update)
int1 >= int2 -> ButtonTag.REINSTALL int2 == int1 || int1 > int2 -> context.getString(R.string.button_reinstall)
else -> ButtonTag.INSTALL 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( data class LinkModel(
val linkIcon: Drawable?, val linkIcon: Drawable?,
val linkUrl: String val linkUrl: 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 package com.vanced.manager.model
import android.content.Context import android.content.Context
import androidx.annotation.DrawableRes import android.graphics.drawable.Drawable
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.beust.klaxon.JsonObject import com.beust.klaxon.JsonObject
@ -10,18 +10,18 @@ import com.vanced.manager.utils.PackageHelper
class RootDataModel( class RootDataModel(
jsonObject: LiveData<JsonObject?>, jsonObject: LiveData<JsonObject?>,
context: Context, context: Context,
lifecycleOwner: LifecycleOwner,
appPkg: String, appPkg: String,
appName: String, appName: String,
appDescription: String, appIcon: Drawable?,
@DrawableRes appIcon: Int,
//BUG THIS! //BUG THIS!
//kotlin thinks that this value is null if we use //kotlin thinks that this value is null if we use
//private val scriptName: String //private val scriptName: String
//Although it's impossible for it to be null. //Although it's impossible for it to be null.
//Ironic, isn't it? //Ironic, isn't it?
private val scriptName: String? private val scriptName: String?
) : DataModel( ): DataModel(
jsonObject, context, appPkg, appName, appDescription, appIcon jsonObject, context, lifecycleOwner, appPkg, appName, appIcon
) { ) {
override fun isAppInstalled(pkg: String): Boolean { override fun isAppInstalled(pkg: String): Boolean {

View file

@ -1,12 +1,10 @@
package com.vanced.manager.ui package com.vanced.manager.ui
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -15,7 +13,10 @@ import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager.getDefaultSharedPreferences import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.crowdin.platform.LoadingStateListener
import com.google.firebase.messaging.FirebaseMessaging 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.BuildConfig.VERSION_CODE
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.databinding.ActivityMainBinding 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.HomeFragmentDirections
import com.vanced.manager.ui.fragments.SettingsFragmentDirections import com.vanced.manager.ui.fragments.SettingsFragmentDirections
import com.vanced.manager.utils.* 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() { class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding lateinit var binding: ActivityMainBinding
private val navHost by lazy { findNavController(R.id.nav_host) } 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?) { override fun onCreate(savedInstanceState: Bundle?) {
setFinalTheme() setFinalTheme()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (ENABLE_CROWDIN_AUTH)
authCrowdin()
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -58,7 +68,7 @@ class MainActivity : AppCompatActivity() {
initDialogs(intent.getBooleanExtra("firstLaunch", false)) initDialogs(intent.getBooleanExtra("firstLaunch", false))
manager.observe(this) { manager.observe(this) {
if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) { 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) { private fun setDisplayHomeAsUpEnabled(isNeeded: Boolean) {
binding.toolbar.navigationIcon = if (isNeeded) ContextCompat.getDrawable( binding.toolbar.navigationIcon = if (isNeeded) ContextCompat.getDrawable(this, R.drawable.ic_keyboard_backspace_black_24dp) else null
this, }
R.drawable.ic_keyboard_backspace_black_24dp
) else null override fun onPause() {
super.onPause()
Crowdin.unregisterDataLoadingObserver(loadingObserver)
} }
override fun onResume() { override fun onResume() {
setFinalTheme() setFinalTheme()
super.onResume() super.onResume()
Crowdin.registerDataLoadingObserver(loadingObserver)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { 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 -> { R.id.toolbar_about -> {
navHost.navigate(HomeFragmentDirections.toAboutFragment()) navHost.navigate(HomeFragmentDirections.toAboutFragment())
true return true
} }
R.id.toolbar_settings -> { R.id.toolbar_settings -> {
navHost.navigate(HomeFragmentDirections.toSettingsFragment()) navHost.navigate(HomeFragmentDirections.toSettingsFragment())
true return true
}
R.id.toolbar_log -> {
navHost.navigate(HomeFragmentDirections.toLogFragment())
true
} }
R.id.toolbar_update_manager -> { R.id.toolbar_update_manager -> {
ManagerUpdateDialog.newInstance(false) ManagerUpdateDialog.newInstance(false).show(supportFragmentManager, "manager_update")
.show(supportFragmentManager, "manager_update")
true
} }
R.id.dev_settings -> { R.id.dev_settings -> {
navHost.navigate(SettingsFragmentDirections.toDevSettingsFragment()) navHost.navigate(SettingsFragmentDirections.toDevSettingsFragment())
@ -105,34 +116,22 @@ class MainActivity : AppCompatActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
return false
} }
override fun attachBaseContext(newBase: Context) { 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) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
recreate() //restarting activity to recreate viewmodels, otherwise some text won't update
//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]")
}
} }
override fun recreate() { override fun recreate() {
@ -154,25 +153,21 @@ class MainActivity : AppCompatActivity() {
urldialog.show(this) urldialog.show(this)
} }
if (firstLaunch) { when {
firstLaunch -> {
DialogContainer.showSecurityDialog(this) DialogContainer.showSecurityDialog(this)
with(FirebaseMessaging.getInstance()) { with(FirebaseMessaging.getInstance()) {
subscribeToTopic("Vanced-Update") subscribeToTopic("Vanced-Update")
subscribeToTopic("Music-Update") subscribeToTopic("Music-Update")
subscribeToTopic("MicroG-Update") subscribeToTopic("MicroG-Update")
} }
} else {
if (isMiuiOptimizationsEnabled) {
DialogContainer.miuiDialog(this)
} }
} !prefs.getBoolean("statement", true) -> DialogContainer.statementFalse(this)
variant == "root" -> {
if (!prefs.getBoolean("statement", true)) { if (PackageHelper.getPackageVersionName(
DialogContainer.statementFalse(this) "com.google.android.youtube",
} packageManager
) == "14.21.54")
if (variant == "root") {
if (PackageHelper.getPackageVersionName(vancedRootPkg, packageManager) == "14.21.54") {
DialogContainer.basicDialog( DialogContainer.basicDialog(
getString(R.string.hold_on), getString(R.string.hold_on),
getString(R.string.magisk_vanced), getString(R.string.magisk_vanced),

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 package com.vanced.manager.ui
import android.animation.ValueAnimator
import android.os.Bundle import android.os.Bundle
import android.util.LayoutDirection
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.addListener import androidx.navigation.findNavController
import androidx.viewpager2.widget.ViewPager2 import com.vanced.manager.R
import com.vanced.manager.adapter.WelcomePageAdapter
import com.vanced.manager.databinding.ActivityWelcomeBinding
import kotlin.math.abs
class WelcomeActivity : AppCompatActivity() { class WelcomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityWelcomeBinding private val navHost by lazy { findNavController(R.id.welcome_navhost) }
private var isRtl = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater) setContentView(R.layout.activity_welcome)
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
}
}
}
}
}
} }
override fun onBackPressed() { override fun onBackPressed() {
with(binding) { if (!navHost.popBackStack())
if (welcomeViewpager.currentItem == 0) { finish()
super.onBackPressed()
} else {
navigateTo(welcomeViewpager.currentItem - 1)
} }
}
}
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) { fun setSummary(newSummary: String) {
with(binding) { with (binding) {
preferenceSummary.text = newSummary preferenceSummary.text = newSummary
preferenceSummary.isVisible = true preferenceSummary.isVisible = true
preferenceTitle.setPadding(0, 0, 0, 0) preferenceTitle.setPadding(0, 0, 0, 0)
@ -40,11 +40,10 @@ class EmptyPreference @JvmOverloads constructor(
private fun initAttrs(context: Context, attrs: AttributeSet?) { private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs?.let { mAttrs -> attrs?.let { mAttrs ->
val typedArray = val typedArray = context.obtainStyledAttributes(mAttrs, R.styleable.EmptyPreference, 0, 0)
context.obtainStyledAttributes(mAttrs, R.styleable.EmptyPreference, 0, 0)
val title = typedArray.getText(R.styleable.EmptyPreference_preference_title) val title = typedArray.getText(R.styleable.EmptyPreference_preference_title)
val summary = typedArray.getText(R.styleable.EmptyPreference_preference_summary) val summary = typedArray.getText(R.styleable.EmptyPreference_preference_summary)
with(binding) { with (binding) {
if (summary != null) { if (summary != null) {
preferenceSummary.text = summary preferenceSummary.text = summary
} else { } else {

View file

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

View file

@ -1,7 +1,6 @@
package com.vanced.manager.ui.core package com.vanced.manager.ui.core
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.CompoundButton import android.widget.CompoundButton
@ -32,13 +31,6 @@ class PreferenceSwitch @JvmOverloads constructor(
private var mListener: OnCheckedListener? = null 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 private var _binding: ViewPreferenceSwitchBinding? = null
val binding: ViewPreferenceSwitchBinding val binding: ViewPreferenceSwitchBinding
@ -46,7 +38,6 @@ class PreferenceSwitch @JvmOverloads constructor(
init { init {
_binding = ViewPreferenceSwitchBinding.inflate(LayoutInflater.from(context), this, true) _binding = ViewPreferenceSwitchBinding.inflate(LayoutInflater.from(context), this, true)
prefs.registerOnSharedPreferenceChangeListener(prefListener)
attrs?.let { mAttrs -> attrs?.let { mAttrs ->
with(context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceSwitch, 0, 0)) { with(context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceSwitch, 0, 0)) {
val title = getText(R.styleable.PreferenceSwitch_switch_title) val title = getText(R.styleable.PreferenceSwitch_switch_title)
@ -94,8 +85,4 @@ class PreferenceSwitch @JvmOverloads constructor(
defValue = newVal defValue = newVal
binding.preferenceSwitch.isChecked = prefs.getBoolean(prefKey, 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.google.android.material.button.MaterialButton
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.utils.accentColor import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.lifecycleOwner import com.vanced.manager.utils.lifecycleOwner
import com.vanced.manager.utils.managerAccent
class ThemedMaterialButton @JvmOverloads constructor( class ThemedMaterialButton @JvmOverloads constructor(
context: Context, context: Context,
@ -16,7 +18,8 @@ class ThemedMaterialButton @JvmOverloads constructor(
) : MaterialButton(context, attributeSet, defStyleAttr) { ) : MaterialButton(context, attributeSet, defStyleAttr) {
init { init {
context.lifecycleOwner?.let { owner -> setBgColor(context.defPrefs.managerAccent)
context.lifecycleOwner()?.let { owner ->
accentColor.observe(owner) { color -> accentColor.observe(owner) { color ->
setBgColor(color.toInt()) setBgColor(color.toInt())
} }

View file

@ -5,13 +5,14 @@ import android.content.res.ColorStateList
import android.util.AttributeSet import android.util.AttributeSet
import com.google.android.material.checkbox.MaterialCheckBox import com.google.android.material.checkbox.MaterialCheckBox
import com.vanced.manager.R 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( class ThemedMaterialCheckbox @JvmOverloads constructor(
context: Context, context: Context,
attributeSet: AttributeSet? = null, attributeSet: AttributeSet? = null,
) : MaterialCheckBox(context, attributeSet, R.attr.checkboxStyle) { ) : MaterialCheckBox(context, attributeSet, R.attr.checkboxStyle) {
init { 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 android.util.AttributeSet
import com.google.android.material.radiobutton.MaterialRadioButton import com.google.android.material.radiobutton.MaterialRadioButton
import com.vanced.manager.R 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( class ThemedMaterialRadioButton @JvmOverloads constructor(
context: Context, context: Context,
attributeSet: AttributeSet? = null, attributeSet: AttributeSet? = null,
) : MaterialRadioButton(context, attributeSet, R.attr.radioButtonStyle) { ) : MaterialRadioButton(context, attributeSet, R.attr.radioButtonStyle) {
init { 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.content.res.ColorStateList
import android.util.AttributeSet import android.util.AttributeSet
import com.google.android.material.slider.Slider 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, context: Context,
attributeSet: AttributeSet? = null, attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : Slider(context, attributeSet, defStyleAttr) { ) : Slider(context, attributeSet, defStyleAttr) {
init { init {
val accentValue = ColorStateList.valueOf(accentColor.value!!) thumbStrokeColor = ColorStateList.valueOf(context.defPrefs.managerAccent)
thumbTintList = accentValue
trackActiveTintList = accentValue
trackInactiveTintList = accentValue.withAlpha(70)
haloTintList = accentValue.withAlpha(60)
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -2,10 +2,11 @@ package com.vanced.manager.ui.dialogs
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.DrawableRes import androidx.core.graphics.drawable.toBitmap
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppInfoBinding import com.vanced.manager.databinding.DialogAppInfoBinding
@ -20,15 +21,13 @@ class AppInfoDialog : BindingDialogFragment<DialogAppInfoBinding>() {
fun newInstance( fun newInstance(
appName: String?, appName: String?,
@DrawableRes appIcon: Int?, appIcon: Drawable?,
changelog: String? changelog: String?
): AppInfoDialog = AppInfoDialog().apply { ): AppInfoDialog = AppInfoDialog().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putString(TAG_APP_NAME, appName) putString(TAG_APP_NAME, appName)
putString(TAG_CHANGELOG, changelog) putString(TAG_CHANGELOG, changelog)
if (appIcon != null) { putParcelable(TAG_APP_ICON, appIcon?.toBitmap())
putInt(TAG_APP_ICON, appIcon)
}
} }
} }
} }
@ -48,7 +47,7 @@ class AppInfoDialog : BindingDialogFragment<DialogAppInfoBinding>() {
with(binding) { with(binding) {
aboutAppName.text = getString(R.string.about_app, arguments?.getString(TAG_APP_NAME)) aboutAppName.text = getString(R.string.about_app, arguments?.getString(TAG_APP_NAME))
aboutAppChangelog.text = arguments?.getString(TAG_CHANGELOG) 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.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogBottomRadioButtonBinding import com.vanced.manager.databinding.DialogBottomRadioButtonBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.defPrefs import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.formatVersion import com.vanced.manager.utils.getCheckedButtonTag
class AppVersionSelectorDialog : class AppVersionSelectorDialog : BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
private val prefs by lazy { requireActivity().defPrefs } private val prefs by lazy { requireActivity().defPrefs }
@ -65,7 +63,7 @@ class AppVersionSelectorDialog :
} }
dialogTitle.text = getString(R.string.version) dialogTitle.text = getString(R.string.version)
dialogSave.setOnClickListener { dialogSave.setOnClickListener {
val checkedTag = dialogRadiogroup.checkedButtonTag val checkedTag = dialogRadiogroup.getCheckedButtonTag()
if (checkedTag != null) { if (checkedTag != null) {
prefs.edit { putString("${arguments?.getString(TAG_APP)}_version", checkedTag) } prefs.edit { putString("${arguments?.getString(TAG_APP)}_version", checkedTag) }
} }
@ -77,7 +75,7 @@ class AppVersionSelectorDialog :
private fun loadBoxes() = private fun loadBoxes() =
arguments?.getStringArrayList(TAG_VERSIONS)?.map { version -> arguments?.getStringArrayList(TAG_VERSIONS)?.map { version ->
ThemedMaterialRadioButton(requireActivity()).apply { ThemedMaterialRadioButton(requireActivity()).apply {
text = version.formatVersion(requireActivity()) text = version
tag = version tag = version
textSize = 18f textSize = 18f
} }

View file

@ -5,9 +5,9 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R 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.openUrl
import com.vanced.manager.utils.showWithAccent
object DialogContainer { object DialogContainer {
@ -16,21 +16,26 @@ object DialogContainer {
setTitle(context.resources.getString(R.string.welcome)) setTitle(context.resources.getString(R.string.welcome))
setMessage(context.resources.getString(R.string.security_context)) setMessage(context.resources.getString(R.string.security_context))
setPositiveButton(context.resources.getString(R.string.close)) { dialog, _ -> setPositiveButton(context.resources.getString(R.string.close)) { dialog, _ ->
dialog.cancel() dialog.dismiss()
}
setOnDismissListener {
if (isMiui()) {
applyAccentMiuiDialog(context)
}
} }
setOnCancelListener { setOnCancelListener {
if (context.isMiuiOptimizationsEnabled) { if (isMiui()) {
miuiDialog(context) applyAccentMiuiDialog(context)
} }
} }
create() create()
showWithAccent() applyAccent()
} }
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit { putBoolean("firstLaunch", false) } prefs.edit { putBoolean("firstLaunch", false) }
} }
fun miuiDialog(context: Context) { private fun applyAccentMiuiDialog(context: Context) {
MaterialAlertDialogBuilder(context).apply { MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.miui_one_title)) setTitle(context.getString(R.string.miui_one_title))
setMessage(context.getString(R.string.miui_one)) setMessage(context.getString(R.string.miui_one))
@ -44,7 +49,7 @@ object DialogContainer {
} }
setCancelable(false) setCancelable(false)
create() create()
showWithAccent() applyAccent()
} }
} }
@ -54,7 +59,7 @@ object DialogContainer {
setMessage("So this statement is false huh? I'll go with True!") setMessage("So this statement is false huh? I'll go with True!")
setPositiveButton("wut?") { dialog, _ -> dialog.dismiss() } setPositiveButton("wut?") { dialog, _ -> dialog.dismiss() }
create() create()
showWithAccent() applyAccent()
} }
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -68,54 +73,28 @@ object DialogContainer {
when (msg) { when (msg) {
context.getString(R.string.installation_signature) -> { context.getString(R.string.installation_signature) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ -> setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl( openUrl("https://lmgtfy.com/?q=andnixsh+apk+verification+disable", R.color.Twitter, context)
"https://lmgtfy.com/?q=andnixsh+apk+verification+disable",
R.color.Twitter,
context
)
} }
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() } setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null) if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
} }
context.getString(R.string.installation_miui) -> { context.getString(R.string.installation_miui) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ -> setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl( openUrl("https://telegra.ph/How-to-install-v15-on-MIUI-02-11", R.color.Telegram, context)
"https://telegra.ph/How-to-install-v15-on-MIUI-02-11",
R.color.Telegram,
context
)
} }
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() } setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null) if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
} }
else -> { else -> {
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() } setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null) if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ -> setNegativeButton(context.getString(R.string.advanced)) { _, _ -> basicDialog(context.getString(R.string.advanced), fullMsg, context) }
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
} }
} }
create() create()
showWithAccent() applyAccent()
} }
} }
@ -125,7 +104,7 @@ object DialogContainer {
setMessage(msg) setMessage(msg)
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() } setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
create() 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.defPrefs
import com.vanced.manager.utils.managerVariant import com.vanced.manager.utils.managerVariant
class InstallationFilesDetectedDialog : class InstallationFilesDetectedDialog : BindingBottomSheetDialogFragment<DialogInstallationFilesDetectedBinding>() {
BindingBottomSheetDialogFragment<DialogInstallationFilesDetectedBinding>() {
companion object { companion object {
@ -41,17 +40,15 @@ class InstallationFilesDetectedDialog :
private fun bindData() { private fun bindData() {
with(binding) { with(binding) {
val app = val app = arguments?.getString(TAG_APP) ?: throw IllegalArgumentException("app name is null")
arguments?.getString(TAG_APP) ?: throw IllegalArgumentException("app name is null")
installationDetectedTitle.text = getString(R.string.app_install_files_detected, app) installationDetectedTitle.text = getString(R.string.app_install_files_detected, app)
installationDetectedSummary.text = installationDetectedSummary.text = getString(R.string.app_install_files_detected_summary, app)
getString(R.string.app_install_files_detected_summary, app)
installationDetectedRedownload.setOnClickListener { installationDetectedRedownload.setOnClickListener {
dismiss() dismiss()
when (app) { if (app == getString(R.string.vanced))
getString(R.string.vanced) -> showDialog(VancedPreferencesDialog()) showDialog(VancedPreferencesDialog())
getString(R.string.music) -> showDialog(MusicPreferencesDialog()) else {
else -> showDialog(AppDownloadDialog.newInstance(app)) showDialog(AppDownloadDialog.newInstance(app))
} }
} }
installationDetectedInstall.setOnClickListener { installationDetectedInstall.setOnClickListener {

View file

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

View file

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

View file

@ -1,6 +1,5 @@
package com.vanced.manager.ui.dialogs package com.vanced.manager.ui.dialogs
import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent 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.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogManagerUpdateBinding import com.vanced.manager.databinding.DialogManagerUpdateBinding
import com.vanced.manager.utils.DownloadHelper.downloadManager 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.applyAccent
import com.vanced.manager.utils.currentDownload
import com.vanced.manager.utils.downloadProgress
import com.vanced.manager.utils.manager import com.vanced.manager.utils.manager
class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>() { class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>() {
@ -58,8 +56,7 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData() bindData()
if (arguments?.getBoolean(TAG_FORCE_UPDATE) == true) { if (arguments?.getBoolean(TAG_FORCE_UPDATE) == true) {
binding.managerUpdatePatient.text = binding.managerUpdatePatient.text = requireActivity().getString(R.string.please_be_patient)
requireActivity().getString(R.string.please_be_patient)
downloadManager(requireActivity()) downloadManager(requireActivity())
} else { } else {
checkUpdates() checkUpdates()
@ -71,20 +68,24 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
isCancelable = false isCancelable = false
managerUpdateProgressbar.applyAccent() managerUpdateProgressbar.applyAccent()
managerUpdateCancel.setOnClickListener { managerUpdateCancel.setOnClickListener {
downloadProgress.value = 0 with(downloadProgress.value) {
currentDownload?.cancel() this?.downloadProgress?.value = 0
this?.currentDownload?.cancel()
}
dismiss() dismiss()
} }
bindDownloadProgress() bindDownloadProgress()
} }
} }
@SuppressLint("SetTextI18n")
private fun DialogManagerUpdateBinding.bindDownloadProgress() { private fun DialogManagerUpdateBinding.bindDownloadProgress() {
downloadProgress.observe(viewLifecycleOwner) { with(downloadProgress) {
observe(viewLifecycleOwner) { progressModel ->
progressModel.downloadProgress.observe(viewLifecycleOwner) {
managerUpdateProgressbar.progress = it managerUpdateProgressbar.progress = it
managerUpdateProgressbarContainer.isVisible = it != 0 managerUpdateProgressbar.isVisible = it != 0
managerUpdateProgress.text = "$it%" }
}
} }
} }
@ -93,19 +94,12 @@ class ManagerUpdateDialog : BindingDialogFragment<DialogManagerUpdateBinding>()
registerReceiver() registerReceiver()
} }
override fun onPause() {
super.onPause()
localBroadcastManager.unregisterReceiver(broadcastReceiver)
}
private fun checkUpdates() { private fun checkUpdates() {
if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) { if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) {
binding.managerUpdatePatient.text = binding.managerUpdatePatient.text = requireActivity().getString(R.string.please_be_patient)
requireActivity().getString(R.string.please_be_patient)
downloadManager(requireActivity()) downloadManager(requireActivity())
} else { } else {
binding.managerUpdatePatient.text = binding.managerUpdatePatient.text = requireActivity().getString(R.string.update_not_found)
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.topjohnwu.superuser.Shell
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerVariantBinding import com.vanced.manager.databinding.DialogManagerVariantBinding
import com.vanced.manager.utils.checkedButtonTag import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.managerVariant import com.vanced.manager.utils.managerVariant
class ManagerVariantDialog : BindingBottomSheetDialogFragment<DialogManagerVariantBinding>() { class ManagerVariantDialog : BindingBottomSheetDialogFragment<DialogManagerVariantBinding>() {
@ -37,7 +37,7 @@ class ManagerVariantDialog : BindingBottomSheetDialogFragment<DialogManagerVaria
val variant = prefs.managerVariant val variant = prefs.managerVariant
root.findViewWithTag<MaterialRadioButton>(variant).isChecked = true root.findViewWithTag<MaterialRadioButton>(variant).isChecked = true
variantSave.setOnClickListener { variantSave.setOnClickListener {
val newPref = variantRadiogroup.checkedButtonTag val newPref = variantRadiogroup.getCheckedButtonTag()
if (variant != newPref) { if (variant != newPref) {
prefs.managerVariant = prefs.managerVariant =
if (newPref == "root" && Shell.rootAccess()) { if (newPref == "root" && Shell.rootAccess()) {

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,11 +3,14 @@ package com.vanced.manager.ui.dialogs
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogVancedPreferencesBinding import com.vanced.manager.databinding.DialogVancedPreferencesBinding
import com.vanced.manager.utils.* import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.vancedPkg
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
import java.util.* import java.util.*
class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPreferencesBinding>() { class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPreferencesBinding>() {
@ -35,27 +38,20 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
private fun bindData() { private fun bindData() {
with(binding) { with(binding) {
val showLang = mutableListOf<String>() val showLang = mutableListOf<String>()
installPrefs.lang?.split(", ")?.forEach { lang -> installPrefs.lang?.split(", ")?.toTypedArray()?.forEach { lang ->
val loc = Locale(lang) val loc = Locale(lang)
showLang.add(loc.getDisplayLanguage(loc).capitalize(Locale.ROOT)) showLang.add(loc.getDisplayLanguage(loc).capitalize(Locale.ROOT))
} }
val vancedVersionsConv = vancedVersions.value?.value?.convertToAppVersions() val vancedVersionsConv = vancedVersions.value?.value?.convertToAppVersions()
vancedInstallTitle.text = vancedInstallTitle.text = getString(R.string.app_installation_preferences, getString(R.string.vanced))
getString(R.string.app_installation_preferences, getString(R.string.vanced)) vancedTheme.text = getString(R.string.chosen_theme, installPrefs.getString("theme", "dark")?.convertToAppTheme(requireActivity()))
vancedTheme.text = getString( vancedVersion.text = getString(R.string.chosen_version, defPrefs.getString("vanced_version", "latest"))
R.string.chosen_theme,
installPrefs.theme?.convertToAppTheme(requireActivity())
)
vancedVersion.text = getString(
R.string.chosen_version,
defPrefs.vancedVersion?.formatVersion(requireActivity())
)
vancedLang.text = getString(R.string.chosen_lang, showLang) vancedLang.text = getString(R.string.chosen_lang, showLang)
openThemeSelectorLayout.setOnClickListener { openThemeSelector.setOnClickListener {
dismiss() dismiss()
showDialog(VancedThemeSelectorDialog()) showDialog(VancedThemeSelectorDialog())
} }
openVersionSelectorLayout.setOnClickListener { openVersionSelector.setOnClickListener {
dismiss() dismiss()
showDialog( showDialog(
AppVersionSelectorDialog.newInstance( AppVersionSelectorDialog.newInstance(
@ -64,7 +60,7 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
) )
) )
} }
openLanguageSelectorLayout.setOnClickListener { openLanguageSelector.setOnClickListener {
dismiss() dismiss()
showDialog(VancedLanguageSelectionDialog()) showDialog(VancedLanguageSelectionDialog())
} }
@ -72,13 +68,34 @@ class VancedPreferencesDialog : BindingBottomSheetDialogFragment<DialogVancedPre
if (showLang.isEmpty()) { if (showLang.isEmpty()) {
installPrefs.lang = "en" installPrefs.lang = "en"
} }
fun downloadVanced(version: String? = null) {
dismiss() dismiss()
showDialog( showDialog(
AppDownloadDialog.newInstance( AppDownloadDialog.newInstance(
app = getString(R.string.vanced) 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.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogBottomRadioButtonBinding import com.vanced.manager.databinding.DialogBottomRadioButtonBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.convertToAppTheme import com.vanced.manager.utils.convertToAppTheme
import com.vanced.manager.utils.getCheckedButtonTag
import com.vanced.manager.utils.theme import com.vanced.manager.utils.theme
import com.vanced.manager.utils.vanced import com.vanced.manager.utils.vanced
class VancedThemeSelectorDialog : class VancedThemeSelectorDialog : BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
companion object { companion object {
@ -25,12 +24,7 @@ class VancedThemeSelectorDialog :
} }
} }
private val prefs by lazy { private val prefs by lazy { requireActivity().getSharedPreferences("installPrefs", Context.MODE_PRIVATE) }
requireActivity().getSharedPreferences(
"installPrefs",
Context.MODE_PRIVATE
)
}
override fun binding( override fun binding(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -57,7 +51,7 @@ class VancedThemeSelectorDialog :
tag.isChecked = true tag.isChecked = true
} }
dialogSave.setOnClickListener { dialogSave.setOnClickListener {
val checkedTag = binding.dialogRadiogroup.checkedButtonTag val checkedTag = binding.dialogRadiogroup.getCheckedButtonTag()
if (checkedTag != null) { if (checkedTag != null) {
prefs.theme = checkedTag 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 { ThemedMaterialRadioButton(requireActivity()).apply {
text = theme.convertToAppTheme(requireActivity()) text = theme.convertToAppTheme(requireActivity())
tag = theme 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.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.edit import androidx.core.content.edit
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -36,11 +37,11 @@ class AboutFragment : BindingFragment<FragmentAboutBinding>() {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun dataBind() { private fun dataBind() {
requireActivity().title = getString(R.string.title_about) requireActivity().title = getString(R.string.title_about)
binding.aboutVersionCard.setOnClickListener { binding.aboutHeader.root.setOnClickListener {
showDialog( showDialog(
AppInfoDialog.newInstance( AppInfoDialog.newInstance(
appName = getString(R.string.app_name), appName = getString(R.string.app_name),
appIcon = R.mipmap.ic_launcher, appIcon = AppCompatResources.getDrawable(requireActivity(), R.mipmap.ic_launcher),
changelog = manager.value?.string("changelog") changelog = manager.value?.string("changelog")
) )
) )
@ -60,25 +61,17 @@ class AboutFragment : BindingFragment<FragmentAboutBinding>() {
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val devSettings = prefs.getBoolean("devSettings", false) val devSettings = prefs.getBoolean("devSettings", false)
if (!devSettings) { if (!devSettings) {
Toast.makeText( Toast.makeText(requireContext(), "Dev options unlocked!", Toast.LENGTH_SHORT).show()
requireContext(),
"Dev options unlocked!",
Toast.LENGTH_SHORT
).show()
prefs.edit { putBoolean("devSettings", true) } prefs.edit { putBoolean("devSettings", true) }
} else } else
Toast.makeText( Toast.makeText(requireContext(), "Dev options already unlocked", Toast.LENGTH_SHORT).show()
requireContext(),
"Dev options already unlocked",
Toast.LENGTH_SHORT
).show()
} }
return@setOnTouchListener true return@setOnTouchListener true
} }
false false
} }
binding.aboutGithubButton.setOnClickListener { viewModel.openUrl("https://github.com/YTVanced/VancedInstaller") } binding.aboutSources.aboutGithubButton.setOnClickListener { viewModel.openUrl("https://github.com/YTVanced/VancedInstaller") }
binding.aboutLicenseButton.setOnClickListener { viewModel.openUrl("https://raw.githubusercontent.com/YTVanced/VancedInstaller/dev/LICENSE") } 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.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager.getDefaultSharedPreferences 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.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentDevSettingsBinding import com.vanced.manager.databinding.FragmentDevSettingsBinding
import com.vanced.manager.ui.WelcomeActivity import com.vanced.manager.ui.WelcomeActivity
import com.vanced.manager.ui.dialogs.ManagerUpdateDialog import com.vanced.manager.ui.dialogs.ManagerUpdateDialog
import com.vanced.manager.ui.dialogs.URLChangeDialog import com.vanced.manager.ui.dialogs.URLChangeDialog
import com.vanced.manager.utils.authCrowdin
class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() { class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() {
@ -33,6 +40,7 @@ class DevSettingsFragment : BindingFragment<FragmentDevSettingsBinding>() {
bindWelcomeLauncher() bindWelcomeLauncher()
bindForceUpdate() bindForceUpdate()
bindChannelURL() bindChannelURL()
bindCrowdin()
bindKernelArch() bindKernelArch()
bindAndroidVersion() 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() { private fun FragmentDevSettingsBinding.bindKernelArch() {
val supportedAbis: Array<String> = Build.SUPPORTED_ABIS val supportedAbis: Array<String> = Build.SUPPORTED_ABIS

View file

@ -41,12 +41,7 @@ class GrantRootFragment : BindingFragment<FragmentGrantRootBinding>() {
private fun grantRoot() { private fun grantRoot() {
if (Shell.rootAccess()) { if (Shell.rootAccess()) {
getDefaultSharedPreferences(requireActivity()).edit { getDefaultSharedPreferences(requireActivity()).edit { putString("vanced_variant", "root") }
putString(
"vanced_variant",
"root"
)
}
navigateToFirstLaunch() navigateToFirstLaunch()
} else { } else {
Toast.makeText(requireActivity(), R.string.root_not_granted, Toast.LENGTH_SHORT).show() 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.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager 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.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent import com.google.android.flexbox.JustifyContent
import com.vanced.manager.BuildConfig.VERSION_CODE
import com.vanced.manager.R 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.LinkAdapter
import com.vanced.manager.adapter.SponsorAdapter import com.vanced.manager.adapter.SponsorAdapter
import com.vanced.manager.core.ui.base.BindingFragment 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.databinding.FragmentHomeBinding
import com.vanced.manager.ui.dialogs.AppInfoDialog
import com.vanced.manager.ui.dialogs.DialogContainer.installAlertBuilder import com.vanced.manager.ui.dialogs.DialogContainer.installAlertBuilder
import com.vanced.manager.ui.viewmodels.HomeViewModel 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.isFetching
import com.vanced.manager.utils.manager
class HomeFragment : BindingFragment<FragmentHomeBinding>() { open class HomeFragment : BindingFragment<FragmentHomeBinding>() {
companion object { companion object {
const val INSTALL_FAILED = "INSTALL_FAILED" const val INSTALL_FAILED = "INSTALL_FAILED"
const val REFRESH_HOME = "REFRESH_HOME" 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 localBroadcastManager by lazy { LocalBroadcastManager.getInstance(requireActivity()) }
private val prefs by lazy { PreferenceManager.getDefaultSharedPreferences(requireActivity()) }
private lateinit var tooltip: ViewTooltip
override fun binding( override fun binding(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -53,27 +58,28 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
private fun bindData() { private fun bindData() {
requireActivity().title = getString(R.string.title_home) requireActivity().title = getString(R.string.title_home)
setHasOptionsMenu(true) setHasOptionsMenu(true)
with(binding) { with (binding) {
homeRefresh.setOnRefreshListener { viewModel.fetchData() } homeRefresh.setOnRefreshListener { viewModel.fetchData() }
isFetching.observe(viewLifecycleOwner) { homeRefresh.isRefreshing = it } isFetching.observe(viewLifecycleOwner) { homeRefresh.isRefreshing = it }
tooltip = ViewTooltip
val prefs = PreferenceManager.getDefaultSharedPreferences(context) .on(recyclerAppList)
if (prefs.contains("LastVersionCode")) { .position(ViewTooltip.Position.TOP)
if (prefs.getInt("LastVersionCode", -1) < VERSION_CODE) { .autoHide(false, 0)
showDialog( .color(ResourcesCompat.getColor(requireActivity().resources, R.color.Twitter, null))
AppInfoDialog.newInstance( .withShadow(false)
appName = getString(R.string.app_name), .corner(25)
appIcon = R.mipmap.ic_launcher, .onHide {
changelog = manager.value?.string("changelog") prefs.edit { putBoolean("show_changelog_tooltip", false) }
) }
) .text(requireActivity().getString(R.string.app_changelog_tooltip))
prefs.edit().putInt("LastVersionCode", VERSION_CODE).apply()
if (prefs.getBoolean("show_changelog_tooltip", true)) {
tooltip.show()
} }
} else prefs.edit().putInt("LastVersionCode", VERSION_CODE).apply()
recyclerAppList.apply { recyclerAppList.apply {
layoutManager = LinearLayoutManager(requireActivity()) layoutManager = LinearLayoutManager(requireActivity())
adapter = ExpandableAppListAdapter(requireActivity(), viewModel /*, tooltip*/) adapter = AppListAdapter(requireActivity(), viewModel, viewLifecycleOwner, tooltip)
setHasFixedSize(true) setHasFixedSize(true)
} }
@ -96,12 +102,14 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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() { override fun onPause() {
super.onPause() super.onPause()
localBroadcastManager.unregisterReceiver(broadcastReceiver) localBroadcastManager.unregisterReceiver(broadcastReceiver)
tooltip.close()
} }
override fun onResume() { override fun onResume() {
@ -112,11 +120,7 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { when (intent.action) {
INSTALL_FAILED -> installAlertBuilder( INSTALL_FAILED -> installAlertBuilder(intent.getStringExtra("errorMsg").toString(), intent.getStringExtra("fullErrorMsg"), requireActivity())
intent.getStringExtra("errorMsg").toString(),
intent.getStringExtra("fullErrorMsg"),
requireActivity()
)
REFRESH_HOME -> viewModel.fetchData() 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.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager.getDefaultSharedPreferences import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.adapter.SelectAppsAdapter import com.vanced.manager.adapter.SelectAppsAdapter
import com.vanced.manager.core.ui.base.BindingFragment import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentSelectAppsBinding import com.vanced.manager.databinding.FragmentSelectAppsBinding
import com.vanced.manager.ui.WelcomeActivity
class SelectAppsFragment : BindingFragment<FragmentSelectAppsBinding>() { class SelectAppsFragment : BindingFragment<FragmentSelectAppsBinding>() {
@ -45,14 +45,13 @@ class SelectAppsFragment : BindingFragment<FragmentSelectAppsBinding>() {
private fun actionOnClickAppsFab() { private fun actionOnClickAppsFab() {
if (selectAdapter.apps.all { app -> !app.isChecked }) { if (selectAdapter.apps.all { app -> !app.isChecked }) {
Toast.makeText(requireActivity(), R.string.select_at_least_one_app, Toast.LENGTH_SHORT) Toast.makeText(requireActivity(), R.string.select_at_least_one_app, Toast.LENGTH_SHORT).show()
.show()
return return
} }
val prefs = getDefaultSharedPreferences(requireActivity()) val prefs = getDefaultSharedPreferences(requireActivity())
selectAdapter.apps.forEach { app -> selectAdapter.apps.forEach { app ->
prefs.edit { putBoolean("enable_${app.tag}", app.isChecked) } 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.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
@ -35,7 +34,6 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) } private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) }
private lateinit var variant: String private lateinit var variant: String
private lateinit var parentActivity: FragmentActivity
override fun binding( override fun binding(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -45,7 +43,6 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
override fun otherSetups() { override fun otherSetups() {
setHasOptionsMenu(true) setHasOptionsMenu(true)
parentActivity = requireActivity()
bindData() bindData()
} }
@ -66,8 +63,8 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private fun FragmentSettingsBinding.bindRecycler() { private fun FragmentSettingsBinding.bindRecycler() {
notificationsRecycler.apply { notificationsRecycler.apply {
layoutManager = LinearLayoutManager(parentActivity) layoutManager = LinearLayoutManager(requireActivity())
adapter = GetNotifAdapter(parentActivity) adapter = GetNotifAdapter(requireActivity())
} }
} }
@ -75,7 +72,7 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
firebase.setOnCheckedListener { _, isChecked -> firebase.setOnCheckedListener { _, isChecked ->
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isChecked) FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isChecked)
FirebasePerformance.getInstance().isPerformanceCollectionEnabled = 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() { private fun FragmentSettingsBinding.bindClearFiles() {
clearFiles.setOnClickListener { clearFiles.setOnClickListener {
with(requireActivity()) { with(requireActivity()) {
listOf( listOf("vanced/nonroot", "vanced/root", "music/nonroot", "music/root", "microg").forEach { dir ->
"vanced/nonroot",
"vanced/root",
"music/nonroot",
"music/root",
"microg"
).forEach { dir ->
File(getExternalFilesDir(dir)?.path.toString()).deleteRecursively() File(getExternalFilesDir(dir)?.path.toString()).deleteRecursively()
} }
Toast.makeText(this, getString(R.string.cleared_files), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.cleared_files), Toast.LENGTH_SHORT).show()
@ -125,7 +116,7 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
} }
private fun FragmentSettingsBinding.bindManagerAccentColor() { private fun FragmentSettingsBinding.bindManagerAccentColor() {
managerAccentColor.apply { managerAccentColor.apply{
setSummary(prefs.getInt("manager_accent_color", defAccentColor).toHex()) setSummary(prefs.getInt("manager_accent_color", defAccentColor).toHex())
setOnClickListener { showDialog(ManagerAccentColorDialog()) } setOnClickListener { showDialog(ManagerAccentColorDialog()) }
accentColor.observe(viewLifecycleOwner) { accentColor.observe(viewLifecycleOwner) {
@ -137,15 +128,14 @@ class SettingsFragment : BindingFragment<FragmentSettingsBinding>() {
private fun FragmentSettingsBinding.bindManagerLanguage() { private fun FragmentSettingsBinding.bindManagerLanguage() {
val langPref = prefs.getString("manager_lang", "System Default") val langPref = prefs.getString("manager_lang", "System Default")
managerLanguage.apply { managerLanguage.apply {
setSummary(getLanguageFormat(parentActivity, requireNotNull(langPref))) setSummary(getLanguageFormat(requireActivity(), requireNotNull(langPref)))
setOnClickListener { showDialog(ManagerLanguageDialog()) } setOnClickListener { showDialog(ManagerLanguageDialog()) }
} }
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val devSettings = val devSettings = getDefaultSharedPreferences(requireActivity()).getBoolean("devSettings", false)
getDefaultSharedPreferences(requireActivity()).getBoolean("devSettings", false)
if (devSettings) { if (devSettings) {
inflater.inflate(R.menu.dev_settings_menu, menu) 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.vanced.manager.core.ui.base.BindingFragment import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentWelcomeBinding import com.vanced.manager.databinding.FragmentWelcomeBinding
import com.vanced.manager.ui.WelcomeActivity
class WelcomeFragment : BindingFragment<FragmentWelcomeBinding>() { class WelcomeFragment : BindingFragment<FragmentWelcomeBinding>() {
@ -20,8 +20,10 @@ class WelcomeFragment : BindingFragment<FragmentWelcomeBinding>() {
} }
private fun bindData() { private fun bindData() {
binding.welcomeGetStarted.setOnClickListener { binding.welcomeGetStarted.setOnClickListener { navigateToWelcome() }
(requireActivity() as WelcomeActivity).navigateTo(1)
} }
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.R
import com.vanced.manager.utils.openUrl import com.vanced.manager.utils.openUrl
class AboutViewModel(application: Application) : AndroidViewModel(application) { class AboutViewModel(application: Application): AndroidViewModel(application) {
fun openUrl(url: String) { fun openUrl(url: String) {
openUrl(url, R.color.GitHub, getApplication()) openUrl(url, R.color.GitHub, getApplication())

View file

@ -1,23 +1,25 @@
package com.vanced.manager.ui.viewmodels package com.vanced.manager.ui.viewmodels
import android.app.Application
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ComponentName import android.content.ComponentName
import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.FragmentManager import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.AndroidViewModel import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager.getDefaultSharedPreferences 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.R
import com.vanced.manager.adapter.LinkAdapter.Companion.DISCORD import com.vanced.manager.adapter.LinkAdapter.Companion.DISCORD
import com.vanced.manager.adapter.LinkAdapter.Companion.REDDIT import com.vanced.manager.adapter.LinkAdapter.Companion.REDDIT
import com.vanced.manager.adapter.LinkAdapter.Companion.TELEGRAM import com.vanced.manager.adapter.LinkAdapter.Companion.TELEGRAM
import com.vanced.manager.adapter.LinkAdapter.Companion.TWITTER import com.vanced.manager.adapter.LinkAdapter.Companion.TWITTER
import com.vanced.manager.adapter.SponsorAdapter.Companion.BRAVE 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.DataModel
import com.vanced.manager.model.RootDataModel import com.vanced.manager.model.RootDataModel
import com.vanced.manager.ui.dialogs.AppDownloadDialog 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.MusicPreferencesDialog
import com.vanced.manager.ui.dialogs.VancedPreferencesDialog import com.vanced.manager.ui.dialogs.VancedPreferencesDialog
import com.vanced.manager.utils.* 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.managerPkg
import com.vanced.manager.utils.AppUtils.microgPkg import com.vanced.manager.utils.AppUtils.microgPkg
import com.vanced.manager.utils.AppUtils.musicPkg 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 com.vanced.manager.utils.PackageHelper.vancedInstallFilesExist
import kotlinx.coroutines.launch 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 variant get() = prefs.getString("vanced_variant", "nonroot")
private val context: Context get() = getApplication()
val vancedModel = MutableLiveData<DataModel>() val vancedModel = MutableLiveData<DataModel>()
val vancedRootModel = MutableLiveData<RootDataModel>() val vancedRootModel = MutableLiveData<RootDataModel>()
@ -54,13 +54,14 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
fun fetchData() { fun fetchData() {
viewModelScope.launch { viewModelScope.launch {
loadJson(context) loadJson(activity)
Crowdin.forceUpdate(activity)
} }
} }
private val microgToast = Toast.makeText(context, R.string.no_microg, Toast.LENGTH_LONG) private val microgToast = Toast.makeText(activity, R.string.no_microg, Toast.LENGTH_LONG)
fun openUrl(context: Context, url: String) { fun openUrl(url: String) {
val color: Int = val color: Int =
when (url) { when (url) {
DISCORD -> R.color.Discord DISCORD -> R.color.Discord
@ -71,82 +72,74 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
else -> R.color.Vanced else -> R.color.Vanced
} }
openUrl(url, color, context) openUrl(url, color, activity)
} }
fun launchApp(app: String, isRoot: Boolean) { fun launchApp(app: String, isRoot: Boolean) {
val componentName = when (app) { val componentName = when (app) {
context.getString(R.string.vanced) -> if (isRoot) ComponentName( activity.getString(R.string.vanced) -> if (isRoot) ComponentName(vancedRootPkg, "$vancedRootPkg.HomeActivity") else ComponentName(vancedPkg, "$vancedRootPkg.HomeActivity")
vancedRootPkg, activity.getString(R.string.music) -> if (isRoot) ComponentName(musicRootPkg, "$musicRootPkg.activities.MusicActivity") else ComponentName(musicPkg, "$musicRootPkg.activities.MusicActivity")
"$vancedRootPkg.HomeActivity" activity.getString(R.string.microg) -> ComponentName(microgPkg, "org.microg.gms.ui.SettingsActivity")
) 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"
)
else -> throw IllegalArgumentException("Can't open this app") else -> throw IllegalArgumentException("Can't open this app")
} }
try { try {
context.startActivity(Intent().setComponent(componentName)) activity.startActivity(Intent().setComponent(componentName))
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
log("VMHMV", e.toString()) Log.d("VMHMV", e.toString())
} }
} }
fun openInstallDialog(fragmentManager: FragmentManager, buttonTag: ButtonTag?, app: String) { fun openInstallDialog(view: View, app: String) {
if (variant == "nonroot" && app != context.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) { if (variant == "nonroot" && app != activity.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) {
microgToast.show() microgToast.show()
return return
} }
if (buttonTag == ButtonTag.UPDATE) { if ((view as MaterialButton).text == activity.getString(R.string.update)) {
when (app) { when (app) {
context.getString(R.string.vanced) -> VancedPreferencesDialog().show(fragmentManager) activity.getString(R.string.vanced) -> VancedPreferencesDialog().show(activity)
context.getString(R.string.music) -> MusicPreferencesDialog().show(fragmentManager) activity.getString(R.string.music) -> MusicPreferencesDialog().show(activity)
else -> AppDownloadDialog.newInstance(app).show(fragmentManager) else -> AppDownloadDialog.newInstance(app).show(activity)
} }
return return
} }
when (app) { when (app) {
context.getString(R.string.vanced) -> { activity.getString(R.string.vanced) -> {
when (variant) { when (variant) {
"nonroot" -> { "nonroot" -> {
if (vancedInstallFilesExist(context)) { if (vancedInstallFilesExist(activity)) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager) InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else { } else {
VancedPreferencesDialog().show(fragmentManager) VancedPreferencesDialog().show(activity)
} }
} }
"root" -> { "root" -> {
VancedPreferencesDialog().show(fragmentManager) VancedPreferencesDialog().show(activity)
} }
} }
} }
context.getString(R.string.music) -> { activity.getString(R.string.music) -> {
when (variant) { when (variant) {
"nonroot" -> { "nonroot" -> {
if (musicApkExists(context)) { if (musicApkExists(activity)) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager) InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else { } else {
MusicPreferencesDialog().show(fragmentManager) MusicPreferencesDialog().show(activity)
} }
} }
"root" -> { "root" -> {
MusicPreferencesDialog().show(fragmentManager) MusicPreferencesDialog().show(activity)
} }
} }
} }
context.getString(R.string.microg) -> { activity.getString(R.string.microg) -> {
if (apkExist(context, "microg.apk")) { if (apkExist(activity, "microg.apk")) {
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager) InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else { } 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) { fun uninstallPackage(pkg: String) {
if (variant == "root" && uninstallRootApk(pkg)) { if (variant == "root" && uninstallRootApk(pkg)) {
viewModelScope.launch { loadJson(context) } viewModelScope.launch { loadJson(activity) }
} else { } else {
uninstallApk(pkg, context) uninstallApk(pkg, activity)
} }
} }
init { init {
with(context) { with (activity) {
if (variant == "root") { if (variant == "root") {
vancedRootModel.value = RootDataModel( vancedRootModel.value = RootDataModel(vanced, this, this, vancedRootPkg, this.getString(R.string.vanced), AppCompatResources.getDrawable(this, R.drawable.ic_vanced), "vanced")
vanced, musicRootModel.value = RootDataModel(music, this, this, musicRootPkg, this.getString(R.string.music), AppCompatResources.getDrawable(this, R.drawable.ic_music), "music")
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"
)
} else { } else {
vancedModel.value = DataModel( vancedModel.value = DataModel(vanced, this, this, vancedPkg, this.getString(R.string.vanced), AppCompatResources.getDrawable(this, R.drawable.ic_vanced))
vanced, musicModel.value = DataModel(music, this, this, musicPkg, this.getString(R.string.music), AppCompatResources.getDrawable(this, R.drawable.ic_music))
this, microgModel.value = DataModel(microg, this, this, microgPkg, this.getString(R.string.microg), AppCompatResources.getDrawable(this, R.drawable.ic_microg))
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
)
} }
managerModel.value = DataModel( managerModel.value = DataModel(manager, this, this, managerPkg, this.getString(R.string.app_name), AppCompatResources.getDrawable(this, R.mipmap.ic_launcher))
manager,
this,
managerPkg,
this.getString(R.string.app_name),
"Just manager meh",
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.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.content.pm.PackageInstaller
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 androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.vanced.manager.BuildConfig
import com.vanced.manager.BuildConfig.APPLICATION_ID import com.vanced.manager.BuildConfig.APPLICATION_ID
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.ui.dialogs.AppDownloadDialog import com.vanced.manager.ui.dialogs.AppDownloadDialog
import com.vanced.manager.ui.fragments.HomeFragment import com.vanced.manager.ui.fragments.HomeFragment
import com.vanced.manager.utils.DownloadHelper.downloadProgress
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.security.MessageDigest 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 vancedPkg = "com.vanced.android.youtube"
const val vancedRootPkg = "com.google.android.youtube" const val vancedRootPkg = "com.google.android.youtube"
const val musicPkg = "com.vanced.android.apps.youtube.music" const val musicPkg = "com.vanced.android.apps.youtube.music"
const val musicRootPkg = "com.google.android.apps.youtube.music" const val musicRootPkg = "com.google.android.apps.youtube.music"
const val microgPkg = "com.mgoogle.android.gms" const val microgPkg = "com.mgoogle.android.gms"
const val faqpkg = "com.vanced.faq"
const val managerPkg = APPLICATION_ID const val managerPkg = APPLICATION_ID
const val playStorePkg = "com.android.vending" 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 { fun sendRefresh(context: Context): Job {
return launch { return launch {
delay(700) delay(700)
LocalBroadcastManager.getInstance(context) LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(HomeFragment.REFRESH_HOME))
.sendBroadcast(Intent(HomeFragment.REFRESH_HOME))
} }
} }
fun sendCloseDialog(context: Context): Job { fun sendCloseDialog(context: Context): Job {
return launch { return launch {
delay(700) delay(700)
installing.postValue(false) downloadProgress.value?.installing?.postValue(false)
LocalBroadcastManager.getInstance(context) LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(AppDownloadDialog.CLOSE_DIALOG))
.sendBroadcast(Intent(AppDownloadDialog.CLOSE_DIALOG))
} }
} }
fun sendFailure(error: List<String>, context: Context) { fun sendFailure(status: Int, fullError: String?, context: Context): Job {
sendFailure(error.joinToString(" "), context) //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 { fun sendFailure(error: String, context: Context): Job {
//Delay error broadcast until activity (and fragment) get back to the screen
return launch { return launch {
delay(700) delay(700)
val intent = Intent(HomeFragment.INSTALL_FAILED) val intent = Intent(HomeFragment.INSTALL_FAILED)
@ -102,7 +86,7 @@ object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
private fun printableHexString(data: ByteArray): String { private fun printableHexString(data: ByteArray): String {
// Create Hex String // Create Hex String
val hexString: StringBuilder = StringBuilder() val hexString: StringBuilder = StringBuilder()
for (aMessageDigest: Byte in data) { for (aMessageDigest:Byte in data) {
var h: String = Integer.toHexString(0xFF and aMessageDigest.toInt()) var h: String = Integer.toHexString(0xFF and aMessageDigest.toInt())
while (h.length < 2) while (h.length < 2)
h = "0$h" h = "0$h"
@ -122,7 +106,6 @@ object AppUtils : CoroutineScope by CoroutineScope(Dispatchers.IO) {
} }
private fun getErrorMessage(status: String, context: Context): String { private fun getErrorMessage(status: String, context: Context): String {
log("VMInstall", status)
return when { return when {
status.contains("INSTALL_FAILED_ABORTED") -> context.getString(R.string.installation_aborted) status.contains("INSTALL_FAILED_ABORTED") -> context.getString(R.string.installation_aborted)
status.contains("INSTALL_FAILED_ALREADY_EXISTS") -> context.getString(R.string.installation_conflict) 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("ModApk_Missing") -> context.getString(R.string.modapk_missing)
status.contains("Files_Missing_VA") -> context.getString(R.string.files_missing_va) status.contains("Files_Missing_VA") -> context.getString(R.string.files_missing_va)
status.contains("Path_Missing") -> context.getString(R.string.path_missing) status.contains("Path_Missing") -> context.getString(R.string.path_missing)
status.contains("INSTALL_FAILED_INTERNAL_ERROR: Permission Denied") -> context.getString( else ->
R.string.installation_miui if (isMiui())
) context.getString(R.string.installation_miui)
else -> context.getString(R.string.installation_failed) 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,6 +1,5 @@
package com.vanced.manager.utils package com.vanced.manager.utils
import android.app.PendingIntent
import android.os.Build import android.os.Build
fun getArch(): String = when { fun getArch(): String = when {
@ -8,9 +7,3 @@ fun getArch(): String = when {
Build.SUPPORTED_ABIS.contains("arm64-v8a") -> "arm64_v8a" Build.SUPPORTED_ABIS.contains("arm64-v8a") -> "arm64_v8a"
else -> "armeabi_v7a" 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 package com.vanced.manager.utils
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.MutableLiveData
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.library.network.providers.createService 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 com.vanced.manager.utils.AppUtils.sendCloseDialog
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -38,49 +39,40 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fileFolder: String, fileFolder: String,
fileName: String, fileName: String,
context: Context, context: Context,
onDownloadComplete: () -> Unit = {}, onDownloadComplete: () -> Unit,
onError: (error: String) -> 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 downloadInterface = createService(DownloadHelper::class, baseUrl)
val download = downloadInterface.download(url) val download = downloadInterface.download(url)
currentDownload = download downloadProgress.value?.currentDownload = download
download.enqueue(object : Callback<ResponseBody> { download.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) { if (response.isSuccessful) {
launch { CoroutineScope(Dispatchers.IO).launch {
if (response.body()?.let { if (response.body()?.let { writeFile(it, context.getExternalFilesDir(fileFolder)?.path + "/" + fileName) } == true) {
writeFile(
it,
context.getExternalFilesDir(fileFolder)?.path + "/" + fileName
)
} == true) {
onDownloadComplete() onDownloadComplete()
} else { } else {
onError("Could not save file") onError("Could not save file")
downloadProgress.postValue(0) downloadProgress.value?.downloadProgress?.postValue(0)
log( Log.d("VMDownloader", "Failed to save file: $url")
"VMDownloader",
"Failed to save file: $url\n${response.errorBody()}"
)
} }
} }
} else { } else {
val errorBody = response.errorBody().toString() onError(response.errorBody().toString())
onError(errorBody) downloadProgress.value?.downloadProgress?.postValue(0)
downloadProgress.postValue(0) Log.d("VMDownloader", "Failed to download file: $url")
log("VMDownloader", "Failed to download file: $url\n$errorBody")
} }
} }
override fun onFailure(call: Call<ResponseBody>, t: Throwable) { override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
if (call.isCanceled) { if (call.isCanceled) {
log("VMDownloader", "Download canceled") Log.d("VMDownloader", "Download canceled")
downloadProgress.postValue(0) downloadProgress.value?.downloadProgress?.postValue(0)
} else { } else {
onError(t.stackTraceToString()) onError(t.stackTraceToString())
downloadProgress.postValue(0) downloadProgress.value?.downloadProgress?.postValue(0)
log("VMDownloader", "Failed to download file: $url") 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) { while (inputStream.read(fileReader).also { read = it } != -1) {
outputStream.write(fileReader, 0, read) outputStream.write(fileReader, 0, read)
downloadedBytes += read.toLong() downloadedBytes += read.toLong()
downloadProgress.postValue((downloadedBytes * 100 / totalBytes).toInt()) downloadProgress.value?.downloadProgress?.postValue((downloadedBytes * 100 / totalBytes).toInt())
} }
outputStream.flush() outputStream.flush()
true true
@ -117,15 +109,15 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
} }
} }
val downloadProgress = MutableLiveData<ProgressModel>()
init {
downloadProgress.value = ProgressModel()
}
fun downloadManager(context: Context) { fun downloadManager(context: Context) {
val url = "https://github.com/YTVanced/VancedManager/releases/latest/download/manager.apk" val url = "https://github.com/YTVanced/VancedManager/releases/latest/download/manager.apk"
download( download(url,"https://github.com/YTVanced/VancedManager/", "manager", "manager.apk", context, onDownloadComplete = {
url,
"https://github.com/YTVanced/VancedManager/",
"manager",
"manager.apk",
context,
onDownloadComplete = {
val apk = File("${context.getExternalFilesDir("manager")?.path}/manager.apk") val apk = File("${context.getExternalFilesDir("manager")?.path}/manager.apk")
val uri = val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
@ -137,16 +129,10 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
intent.setDataAndType(uri, "application/vnd.android.package-archive") intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(intent) context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
log("VMDownloader", e.stackTraceToString())
} finally {
sendCloseDialog(context) sendCloseDialog(context)
} }, onError = {
}, downloadProgress.value?.downloadingFile?.postValue(
onError = {
downloadingFile.postValue(
context.getString( context.getString(
R.string.error_downloading, R.string.error_downloading,
"manager.apk" "manager.apk"

View file

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

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 android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
val Context.installPrefs: SharedPreferences val Context.installPrefs: SharedPreferences get() = getSharedPreferences("installPrefs", Context.MODE_PRIVATE)
get() = getSharedPreferences(
"installPrefs",
Context.MODE_PRIVATE
)
var SharedPreferences.lang var SharedPreferences.lang
get() = getString("lang", getDefaultVancedLanguages()) get() = getString("lang", getDefaultVancedLanguages())

View file

@ -15,7 +15,6 @@ import com.beust.klaxon.JsonArray
import com.beust.klaxon.JsonObject import com.beust.klaxon.JsonObject
import com.vanced.manager.R import com.vanced.manager.R
import com.vanced.manager.utils.AppUtils.generateChecksum import com.vanced.manager.utils.AppUtils.generateChecksum
import com.vanced.manager.utils.AppUtils.log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -32,8 +31,8 @@ val music = MutableLiveData<JsonObject?>()
val microg = MutableLiveData<JsonObject?>() val microg = MutableLiveData<JsonObject?>()
val manager = MutableLiveData<JsonObject?>() val manager = MutableLiveData<JsonObject?>()
val vancedVersions = MutableLiveData<JsonArray<String>?>() val vancedVersions = MutableLiveData<JsonArray<String>>()
val musicVersions = MutableLiveData<JsonArray<String>?>() val musicVersions = MutableLiveData<JsonArray<String>>()
val isFetching = MutableLiveData<Boolean>() val isFetching = MutableLiveData<Boolean>()
@ -43,28 +42,19 @@ var baseInstallUrl = ""
fun openUrl(url: String, color: Int, context: Context) { fun openUrl(url: String, color: Int, context: Context) {
try { try {
val customTabPrefs = val customTabPrefs = getDefaultSharedPreferences(context).getBoolean("use_custom_tabs", true)
getDefaultSharedPreferences(context).getBoolean("use_custom_tabs", true)
if (customTabPrefs) { if (customTabPrefs) {
val builder = CustomTabsIntent.Builder() val builder = CustomTabsIntent.Builder()
val params = CustomTabColorSchemeParams.Builder() val params = CustomTabColorSchemeParams.Builder().setToolbarColor(ContextCompat.getColor(context, color))
.setToolbarColor(ContextCompat.getColor(context, color))
builder.setDefaultColorSchemeParams(params.build()) builder.setDefaultColorSchemeParams(params.build())
val customTabsIntent = builder.build() val customTabsIntent = builder.build()
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
customTabsIntent.launchUrl(context, url.toUri()) customTabsIntent.launchUrl(context, url.toUri())
} else } else
context.startActivity( context.startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
Intent(
Intent.ACTION_VIEW,
url.toUri()
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show() 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() connect()
} }
if (connection.responseCode != 200) { 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" baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1"
} }
} catch (e: IOException) { } catch (e: IOException) {
baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1" baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1"
} catch (e: SocketTimeoutException) { } catch (e: SocketTimeoutException) {
log(TAG, "connection timed out") Log.d(TAG, "connection timed out")
baseInstallUrl = "https://mirror.codebucket.de/vanced/api/v1" 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 calendar = Calendar.getInstance()
val hour = calendar.get(Calendar.HOUR_OF_DAY) 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") val versions = getJson("$baseInstallUrl/versions.json?$fetchTime")
isMicrogBroken = latest?.boolean("is_microg_broken") ?: false isMicrogBroken = latest?.boolean("is_microg_broken") ?: false
vanced.postValue(latest?.obj("vanced")) vanced.postValue(latest?.obj("vanced"))
vancedVersions.postValue(versions?.array("vanced")) vancedVersions.postValue(versions?.array("vanced") )
music.postValue(latest?.obj("music")) music.postValue(latest?.obj("music"))
musicVersions.postValue(versions?.array("music")) musicVersions.postValue(versions?.array("music"))
microg.postValue(latest?.obj("microg")) 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.ContextWrapper
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import com.vanced.manager.utils.AppUtils.currentLocale import androidx.preference.PreferenceManager
import com.crowdin.platform.Crowdin
import java.util.* import java.util.*
class LanguageContextWrapper(base: Context?) : ContextWrapper(base) { class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
@ -14,22 +15,20 @@ class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
fun wrap(context: Context): ContextWrapper { fun wrap(context: Context): ContextWrapper {
val config: Configuration = context.resources.configuration val config: Configuration = context.resources.configuration
context.createConfigurationContext(setLocale(config, context)) context.createConfigurationContext(setLocale(config, context))
Crowdin.wrapContext(context)
return LanguageContextWrapper(context) return LanguageContextWrapper(context)
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun setLocale(config: Configuration, context: Context): Configuration { 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 sysLocale = Resources.getSystem().configuration.locale
val locale = when { val locale =
when {
pref == "System Default" -> Locale(sysLocale.language, sysLocale.country) pref == "System Default" -> Locale(sysLocale.language, sysLocale.country)
pref?.length!! > 2 -> Locale( pref?.length!! > 2 -> Locale(pref.substring(0, pref.length - 3), pref.substring(pref.length - 2))
pref.substring(0, pref.length - 3),
pref.substring(pref.length - 2)
)
else -> Locale(pref) else -> Locale(pref)
} }
currentLocale = locale
Locale.setDefault(locale) Locale.setDefault(locale)
config.setLocale(locale) config.setLocale(locale)
return config return config

View file

@ -1,10 +1,15 @@
package com.vanced.manager.utils package com.vanced.manager.utils
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import android.provider.Settings
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.crowdin.platform.Crowdin
import com.vanced.manager.R import com.vanced.manager.R
import java.util.* import java.util.*
@ -29,10 +34,7 @@ fun getLanguageFormat(context: Context, language: String): String {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun getDefaultVancedLanguages(): String { fun getDefaultVancedLanguages(): String {
val serverLangs = vanced.value?.array("langs") ?: mutableListOf("") val serverLangs = vanced.value?.array("langs") ?: mutableListOf("")
val sysLocales = val sysLocales = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Resources.getSystem().configuration.locales.toLangTags() else arrayOf(Resources.getSystem().configuration.locale.language)
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>() val finalLangs = mutableListOf<String>()
sysLocales.forEach { sysLocale -> sysLocales.forEach { sysLocale ->
when { when {
@ -52,3 +54,25 @@ fun LocaleList.toLangTags(): Array<String> {
} }
return langTags 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 package com.vanced.manager.utils
import android.content.Context import java.io.BufferedReader
import android.provider.Settings 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 fun isMiui(): Boolean = !getSystemProps(MIUI_PROP_NAME).isNullOrEmpty()
get() = Settings.Secure.getString(
contentResolver, private fun getSystemProps(propname: String): String? {
MIUI_OPTIMIZATION var input: BufferedReader? = null
) == "1" 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.PackageInstaller
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFile
import com.vanced.manager.BuildConfig
import com.vanced.manager.core.installer.AppInstallerService import com.vanced.manager.core.installer.AppInstallerService
import com.vanced.manager.core.installer.AppUninstallerService 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.musicRootPkg
import com.vanced.manager.utils.AppUtils.playStorePkg import com.vanced.manager.utils.AppUtils.playStorePkg
import com.vanced.manager.utils.AppUtils.sendCloseDialog 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.*
import java.io.FileInputStream import java.text.SimpleDateFormat
import java.io.IOException
import java.io.InputStream
import java.util.* import java.util.*
import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
object PackageHelper { object PackageHelper {
const val apkInstallPath = "/data/adb" const val apkInstallPath = "/data/adb"
private const val INSTALLER_TAG = "VMInstall" private const val INSTALLER_TAG = "VMInstall"
private val vancedThemes = private val vancedThemes = vanced.value?.array<String>("themes")?.value ?: listOf("black", "dark", "pink", "blue")
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 { private fun getAppNameRoot(pkg: String): String {
return when (pkg) { return when (pkg) {
@ -51,6 +61,7 @@ object PackageHelper {
return false return false
} }
fun getPkgNameRoot(app: String): String { fun getPkgNameRoot(app: String): String {
return when (app) { return when (app) {
"vanced" -> vancedRootPkg "vanced" -> vancedRootPkg
@ -58,7 +69,6 @@ object PackageHelper {
else -> "" else -> ""
} }
} }
fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean { fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean {
return try { return try {
packageManager.getPackageInfo(packageName, 0) packageManager.getPackageInfo(packageName, 0)
@ -76,7 +86,7 @@ object PackageHelper {
} }
@Suppress("DEPRECATION") @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) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
pm.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt() pm.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt()
else else
@ -117,15 +127,9 @@ object PackageHelper {
if (files?.isNotEmpty() == true) { if (files?.isNotEmpty() == true) {
for (file in files) { for (file in files) {
when { when {
vancedThemes.any { file.name == "$it.apk" } && !splitFiles.contains("base") -> splitFiles.add( vancedThemes.any { file.name == "$it.apk" } && !splitFiles.contains("base") -> splitFiles.add("base")
"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")
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) { if (splitFiles.size == 3) {
@ -149,7 +153,7 @@ object PackageHelper {
fun uninstallApk(pkg: String, context: Context) { fun uninstallApk(pkg: String, context: Context) {
val callbackIntent = Intent(context, AppUninstallerService::class.java) val callbackIntent = Intent(context, AppUninstallerService::class.java)
callbackIntent.putExtra("pkg", pkg) callbackIntent.putExtra("pkg", pkg)
val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, intentFlags) val pendingIntent = PendingIntent.getService(context, 0, callbackIntent, 0)
try { try {
context.packageManager.packageInstaller.uninstall(pkg, pendingIntent.intentSender) context.packageManager.packageInstaller.uninstall(pkg, pendingIntent.intentSender)
} catch (e: Exception) { } catch (e: Exception) {
@ -158,94 +162,73 @@ object PackageHelper {
} }
fun install(path: String, context: Context) { 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 { try {
sessionId = packageInstaller.createSession(params) val callbackIntent = Intent(context, AppInstallerService::class.java)
session = packageInstaller.openSession(sessionId) 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 inputStream: InputStream = FileInputStream(path)
val outputStream = session.openWrite("install", 0, -1) val outputStream = session.openWrite("install", 0, -1)
val buffer = ByteArray(65536) val buffer = ByteArray(65536)
var length: Int var c: Int
while (inputStream.read(buffer).also { length = it } > 0) { while (inputStream.read(buffer).also { c = it } != -1) {
outputStream.write(buffer, 0, length) outputStream.write(buffer, 0, c)
} }
session.fsync(outputStream) session.fsync(outputStream)
inputStream.close() inputStream.close()
outputStream.close() outputStream.close()
session.commit(pendingIntent.intentSender) session.commit(pendingIntent.intentSender)
} catch (e: Exception) { } catch (e: IOException) {
log(INSTALLER_TAG, e.stackTraceToString()) Log.d(INSTALLER_TAG, e.stackTraceToString())
sendFailure(e.stackTraceToString(), context)
sendCloseDialog(context)
} finally {
session?.close()
}
} }
private fun installRootMusic(files: List<File>, context: Context): Boolean { }
private fun installRootMusic(files: ArrayList<FileInfo>, context: Context): Boolean {
files.forEach { apk -> files.forEach { apk ->
if (apk.name != "root.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 //moving apk to tmp folder in order to avoid permission denials
Shell.su("cp ${apk.path} $newPath").exec() Shell.su("mv ${apk.file?.path} $newPath").exec()
val command = Shell.su("pm install -r $newPath").exec() val command = Shell.su("pm install $newPath").exec()
Shell.su("rm $newPath").exec() Shell.su("rm $newPath").exec()
if (!command.isSuccess) { if (command.isSuccess) {
return true
} else {
sendFailure(command.out, context) sendFailure(command.out, context)
sendCloseDialog(context) sendCloseDialog(context)
}
}
}
return false return false
} }
}
}
return true
}
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
}
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 apkFilesPath = context.getExternalFilesDir("$app/root")?.path
val files = File(apkFilesPath.toString()).listFiles()?.toList() val fileInfoList = apkFilesPath?.let { it1 -> getFileInfoList(it1) }
if (files != null) { if (fileInfoList != null) {
val modApk: File? = files.lastOrNull { modApkBool(it.name) } val modApk: FileInfo? = fileInfoList.lastOrNull { modApkBool(it.name) }
if (modApk != null) { if (modApk != null) {
if (appVerCode != null) { if (overwriteBase(modApk, fileInfoList, appVerCode, pkg, app, context)) {
if (overwriteBase(modApk, files, appVerCode, pkg, app, context)) {
setInstallerPackage(context, pkg, playStorePkg) setInstallerPackage(context, pkg, playStorePkg)
log(INSTALLER_TAG, "Finished installation") Log.d(INSTALLER_TAG, "Finished installation")
sendRefresh(context) sendRefresh(context)
sendCloseDialog(context) sendCloseDialog(context)
} }
} else { } else {
sendFailure("appVerCode is null", context) sendFailure(listOf("ModApk_Missing").toMutableList(), context)
sendCloseDialog(context) sendCloseDialog(context)
} }
} else { } else {
sendFailure("ModApk_Missing", context) sendFailure(listOf("Files_Missing_VA").toMutableList(), context)
sendCloseDialog(context) sendCloseDialog(context)
} }
} else {
sendFailure("Files_Missing_VA", context)
sendCloseDialog(context)
} }
} }
@ -254,7 +237,7 @@ object PackageHelper {
installRootApp( installRootApp(
context, context,
"music", "music",
music.value?.int("versionCode"), music.value?.int("versionCode")!!,
musicRootPkg musicRootPkg
) { ) {
it == "root.apk" it == "root.apk"
@ -265,95 +248,202 @@ object PackageHelper {
installRootApp( installRootApp(
context, context,
"vanced", "vanced",
vanced.value?.int("versionCode"), vanced.value?.int("versionCode")!!,
vancedRootPkg vancedRootPkg
) { fileName -> ) { fileName ->
vancedThemes.any { fileName == "$it.apk" } vancedThemes.any { fileName == "$it.apk" }
} }
} }
fun installSplitApkFiles( fun installVanced(context: Context): Int {
context: Context, val apkFolderPath = context.getExternalFilesDir("vanced/nonroot")?.path.toString() + "/"
appName: String val nameSizeMap = HashMap<String, Long>()
) { var totalSize: Long = 0
val packageInstaller = context.packageManager.packageInstaller var sessionId = 0
val folder = File(context.getExternalFilesDir("$appName/nonroot")?.path.toString()) val folder = File(apkFolderPath)
var session: PackageInstaller.Session? = null val listOfFiles = folder.listFiles()
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)
try { try {
sessionId = packageInstaller.createSession(sessionParams) for (listOfFile in listOfFiles!!) {
session = packageInstaller.openSession(sessionId) if (listOfFile.isFile) {
folder.listFiles()?.forEach { apk -> Log.d(INSTALLER_TAG, "installApk: " + listOfFile.name)
val inputStream = FileInputStream(apk) nameSizeMap[listOfFile.name] = listOfFile.length()
val outputStream = session.openWrite(apk.name, 0, apk.length()) totalSize += listOfFile.length()
val buffer = ByteArray(65536)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
} }
session.fsync(outputStream)
inputStream.close()
outputStream.close()
} }
session.commit(pendingIntent.intentSender)
} catch (e: Exception) { } catch (e: Exception) {
log(INSTALLER_TAG, e.stackTraceToString()) e.printStackTrace()
sendFailure(e.stackTraceToString(), context) return -1
sendCloseDialog(context) }
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 { } finally {
session?.close() 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") val filenames = arrayOf("black.apk", "dark.apk", "blue.apk", "pink.apk", "hash.json")
log(INSTALLER_TAG, "installing split apk files: ${apkFiles?.map { it.name }}") Log.d(INSTALLER_TAG, "installing split apk files: $apkFiles")
val sessionId = run {
Shell.su("pm install-create -r").exec().out.joinToString(" ").filter { it.isDigit() } val sessionIdResult = Shell.su("pm install-create -r -t").exec().out
.toIntOrNull() 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()
if (sessionId == null) { sendFailure(e.stackTrace.map { it.toString() }.toMutableList(), context)
sendFailure("Session ID is null", context)
sendCloseDialog(context) sendCloseDialog(context)
return false
} }
process.waitFor()
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
} }
} }
log(INSTALLER_TAG, "committing...") Log.d(INSTALLER_TAG, "committing...")
val installResult = Shell.su("pm install-commit $sessionId").exec() val installResult = Shell.su("pm install-commit $sessionId").exec()
if (!installResult.isSuccess) { if (installResult.isSuccess) {
return true
}
sendFailure(installResult.out, context) sendFailure(installResult.out, context)
sendCloseDialog(context) sendCloseDialog(context)
return false return false
} }
return true
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 //overwrite stock Vanced/Music
private fun overwriteBase( private fun overwriteBase(
apkFile: File, apkFile: FileInfo,
baseApkFiles: List<File>, baseApkFiles: ArrayList<FileInfo>,
versionCode: Int, versionCode: Int,
pkg: String, pkg: String,
app: String, app: String,
@ -361,7 +451,8 @@ object PackageHelper {
): Boolean { ): Boolean {
if (checkVersion(versionCode, baseApkFiles, pkg, context)) { if (checkVersion(versionCode, baseApkFiles, pkg, context)) {
val path = getPackageDir(context, pkg) val path = getPackageDir(context, pkg)
val apath = apkFile.absolutePath apkFile.file?.let {
val apath = it.absolutePath
setupFolder("$apkInstallPath/${app.capitalize(Locale.ROOT)}") setupFolder("$apkInstallPath/${app.capitalize(Locale.ROOT)}")
if (path != null) { if (path != null) {
@ -375,35 +466,27 @@ object PackageHelper {
} }
} }
} }
}
return false return false
} }
private fun setupScript( private fun setupScript(apkFPath: String, path: String, app: String, pkg: String, context: Context): Boolean
apkFPath: String, {
path: String,
app: String,
pkg: String,
context: Context
): Boolean {
try { try {
log(INSTALLER_TAG, "Setting up script") Log.d(INSTALLER_TAG, "Setting up script")
context.writeServiceDScript(apkFPath, path, app) 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""") 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()
.exec()
return Shell.su("chmod 744 /data/adb/service.d/$app.sh").exec().isSuccess return Shell.su("chmod 744 /data/adb/service.d/$app.sh").exec().isSuccess
} catch (e: IOException) { } catch (e: IOException) {
sendFailure(e.stackTraceToString(), context) e.printStackTrace()
sendCloseDialog(context)
log(INSTALLER_TAG, e.stackTraceToString())
} }
return false return false
} }
private fun linkApp(apkFPath: String, pkg: String, path: String): Boolean { 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("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 """) Shell.su("""for i in ${'$'}(ls /data/app/ | grep $pkg | tr " "); do umount -l "/data/app/${"$"}i/base.apk"; done """).exec()
.exec()
val response = Shell.su("""su -mm -c "mount -o bind $apkFPath $path"""").exec() val response = Shell.su("""su -mm -c "mount -o bind $apkFPath $path"""").exec()
Thread.sleep(500) Thread.sleep(500)
Shell.su("am force-stop $pkg").exec() Shell.su("am force-stop $pkg").exec()
@ -415,17 +498,12 @@ object PackageHelper {
} }
//check version and perform action based on result //check version and perform action based on result
private fun checkVersion( private fun checkVersion(versionCode: Int, baseApkFiles: ArrayList<FileInfo>, pkg: String, context: Context): Boolean {
versionCode: Int, Log.d(INSTALLER_TAG, "Checking stock version")
baseApkFiles: List<File>,
pkg: String,
context: Context
): Boolean {
log(INSTALLER_TAG, "Checking stock version")
val path = getPackageDir(context, pkg) val path = getPackageDir(context, pkg)
if (path != null) { if (path != null) {
if (path.contains("/data/app/")) { 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 fixHigherVer(baseApkFiles, pkg, context)
-1 -> return installStock(baseApkFiles, pkg, context) -1 -> return installStock(baseApkFiles, pkg, context)
} }
@ -439,8 +517,8 @@ object PackageHelper {
private fun getPkgInfo(pkg: String, context: Context): PackageInfo? { private fun getPkgInfo(pkg: String, context: Context): PackageInfo? {
return try { return try {
context.packageManager.getPackageInfo(pkg, 0) context.packageManager.getPackageInfo(pkg, 0)
} catch (e: Exception) { } catch (e:Exception) {
log(INSTALLER_TAG, "Unable to get package info") Log.d(INSTALLER_TAG, "Unable to get package info")
null null
} }
} }
@ -454,13 +532,10 @@ object PackageHelper {
} }
//uninstall current update and install base that works with patch //uninstall current update and install base that works with patch
private fun fixHigherVer(apkFiles: List<File>, pkg: String, context: Context): Boolean { private fun fixHigherVer(apkFiles: ArrayList<FileInfo>, pkg: String, context: Context) : Boolean {
log(INSTALLER_TAG, "Downgrading stock") Log.d(INSTALLER_TAG, "Downgrading stock")
if (uninstallRootApk(pkg)) { if (uninstallRootApk(pkg)) {
return if (pkg == vancedRootPkg) installSplitApkFilesRoot( return if (pkg == vancedRootPkg) installSplitApkFiles(apkFiles, context) else installRootMusic(apkFiles, context)
apkFiles,
context
) else installRootMusic(apkFiles, context)
} }
sendFailure(listOf("Failed_Uninstall").toMutableList(), context) sendFailure(listOf("Failed_Uninstall").toMutableList(), context)
sendCloseDialog(context) sendCloseDialog(context)
@ -468,70 +543,78 @@ object PackageHelper {
} }
//install stock youtube matching vanced version //install stock youtube matching vanced version
private fun installStock(baseApkFiles: List<File>, pkg: String, context: Context): Boolean { private fun installStock(baseApkFiles: ArrayList<FileInfo>, pkg: String, context: Context): Boolean {
log(INSTALLER_TAG, "Installing stock") Log.d(INSTALLER_TAG, "Installing stock")
return if (pkg == vancedRootPkg) installSplitApkFilesRoot( return if (pkg == vancedRootPkg) installSplitApkFiles(baseApkFiles, context) else installRootMusic(baseApkFiles, context)
baseApkFiles,
context
) else installRootMusic(baseApkFiles, context)
} }
private fun isMagiskInstalled() = Shell.su("magisk -c").exec().isSuccess
//set chcon to apk_data_file //set chcon to apk_data_file
private fun chConV(apkFPath: String, context: Context): Boolean { 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 u:object_r:apk_data_file:s0 $apkFPath").exec()
//val response = Shell.su("chcon -R u:object_r:system_file:s0 $path").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) sendFailure(response.out, context)
sendCloseDialog(context) sendCloseDialog(context)
return false false
} }
return true
} }
//move patch to data/app //move patch to data/app
private fun moveAPK(apkFile: String, path: String, pkg: String, context: Context): Boolean { private fun moveAPK(apkFile: String, path: String, pkg: String, context: Context) : Boolean {
log(INSTALLER_TAG, "Moving app") Log.d(INSTALLER_TAG, "Moving app")
val apkinF = SuFile.open(apkFile)
val apkoutF = SuFile.open(path)
if(apkinF.exists()) {
try {
Shell.su("am force-stop $pkg").exec() Shell.su("am force-stop $pkg").exec()
val mv = Shell.su("cp $apkFile $path").exec() //Shell.su("rm -r SuFile.open(path).parent")
if (!mv.isSuccess) {
sendFailure(mv.out.apply { add(0, "MV_Fail") }, context) 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) sendCloseDialog(context)
return false 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() @Throws(IOException::class)
if (!chown.isSuccess) { fun copy(src: File, dst: File) {
sendFailure(chown.out.apply { add(0, "Chown_Fail") }, context) val cmd = Shell.su("mv ${src.absolutePath} ${dst.absolutePath}").exec().isSuccess
sendCloseDialog(context) Log.d("ZLog", cmd.toString())
return false
}
return true
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun getVersionNumber(pkg: String, context: Context): Int? { private fun getVersionNumber(pkg: String, context: Context): Int? {
try { try {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
context.packageManager.getPackageInfo(vancedRootPkg, 0).longVersionCode.and( context.packageManager.getPackageInfo(vancedRootPkg, 0).longVersionCode.and(0xFFFFFFFF).toInt()
0xFFFFFFFF
).toInt()
else else
context.packageManager.getPackageInfo(vancedRootPkg, 0).versionCode context.packageManager.getPackageInfo(vancedRootPkg, 0).versionCode
} catch (e: Exception) { }
catch (e : Exception) {
val execRes = Shell.su("dumpsys package $pkg | grep versionCode").exec() val execRes = Shell.su("dumpsys package $pkg | grep versionCode").exec()
if (execRes.isSuccess) { if(execRes.isSuccess) {
val result = execRes.out val result = execRes.out
var version = 0 var version = 0
result result
@ -547,16 +630,22 @@ object PackageHelper {
} }
//get path of the installed youtube //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) val p = getPkgInfo(pkg, context)
return if (p != null) { return if(p != null)
{
p.applicationInfo.sourceDir p.applicationInfo.sourceDir
} else { }
else
{
val execRes = Shell.su("dumpsys package $pkg | grep codePath").exec() val execRes = Shell.su("dumpsys package $pkg | grep codePath").exec()
if (execRes.isSuccess) { if(execRes.isSuccess)
{
val result = execRes.out val result = execRes.out
for (line in result) { for (line in result)
if (line.contains("data/app")) "${line.substringAfter("=")}/base.apk" {
if(line.contains("data/app")) "${line.substringAfter("=")}/base.apk"
} }
} }
null null
@ -566,17 +655,16 @@ object PackageHelper {
private fun setInstallerPackage(context: Context, target: String, installer: String) { private fun setInstallerPackage(context: Context, target: String, installer: String) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
try { 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 installerUid = context.packageManager.getPackageUid(installer, 0)
val res = val res = Shell.su("""su $installerUid -c 'pm set-installer $target $installer'""").exec()
Shell.su("""su $installerUid -c 'pm set-installer $target $installer'""").exec()
if (res.out.any { line -> line.contains("Success") }) { if (res.out.any { line -> line.contains("Success") }) {
log(INSTALLER_TAG, "Installer package successfully set") Log.d(INSTALLER_TAG, "Installer package successfully set")
return return
} }
log(INSTALLER_TAG, "Failed setting installer package") Log.d(INSTALLER_TAG, "Failed setting installer package")
} catch (e: PackageManager.NameNotFoundException) { } 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

@ -1,22 +0,0 @@
package com.vanced.manager.utils
import androidx.lifecycle.MutableLiveData
import okhttp3.ResponseBody
import retrofit2.Call
val downloadProgress = MutableLiveData(0)
val downloadingFile = MutableLiveData("")
val installing = MutableLiveData(false)
var currentDownload: Call<ResponseBody>? = null
fun reset() {
downloadProgress.value = 0
downloadingFile.value = ""
}
fun postReset() {
downloadProgress.postValue(0)
downloadingFile.postValue("")
}

View file

@ -7,30 +7,20 @@ import androidx.lifecycle.MutableLiveData
import com.vanced.manager.R import com.vanced.manager.R
const val defAccentColor: Int = -13732865 const val defAccentColor: Int = -13732865
const val LIGHT = "Light"
const val DARK = "Dark"
const val SYSTEM_DEFAULT = "System Default"
val mutableAccentColor = MutableLiveData(defAccentColor) val mutableAccentColor = MutableLiveData<Int>()
val accentColor: LiveData<Int> = mutableAccentColor val accentColor: LiveData<Int> = mutableAccentColor
var currentTheme = ""
fun Activity.setFinalTheme() { fun Activity.setFinalTheme() {
when (defPrefs.managerTheme) { when (defPrefs.managerTheme) {
LIGHT -> setTheme(R.style.LightTheme, LIGHT) "Light" -> setTheme(R.style.LightTheme)
DARK -> setTheme(R.style.DarkTheme, DARK) "Dark" -> setTheme(R.style.DarkTheme)
SYSTEM_DEFAULT -> { "System Default" -> {
when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> setTheme(R.style.DarkTheme, DARK) Configuration.UI_MODE_NIGHT_YES -> setTheme(R.style.DarkTheme)
Configuration.UI_MODE_NIGHT_NO -> setTheme(R.style.LightTheme, LIGHT) Configuration.UI_MODE_NIGHT_NO -> setTheme(R.style.LightTheme)
} }
} }
else -> setTheme(R.style.LightTheme, LIGHT) else -> setTheme(R.style.LightTheme)
} }
} }
fun Activity.setTheme(resId: Int, themeValue: String) {
setTheme(resId)
currentTheme = themeValue
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="@android:integer/config_mediumAnimTime"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="0"
android:propertyName="xFraction" />
</set>

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