initial compose commit

This commit is contained in:
X1nto 2021-05-30 20:21:05 +04:00
parent e6f04a95c8
commit 092548e8bf
282 changed files with 3094 additions and 10720 deletions

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

6
.gitignore vendored
View File

@ -1,10 +1,4 @@
.gradle/
.idea/
build/
out/
app/src/main/java/com/vanced/manager/core/base/DummyJava.java
app/build/
app/release
local.properties
/.github/
*.iml

View File

@ -1,10 +1,6 @@
# Vanced Manager
<div>
[![Github All Releases](https://img.shields.io/github/downloads/YTVanced/VancedManager/total.svg?style=for-the-badge)](https://github.com/YTVanced/VancedManager/releases/latest) [![Github All Releases](https://img.shields.io/github/release/YTVanced/VancedManager.svg?style=for-the-badge)](https://github.com/YTVanced/VancedManager/releases/latest)
</div>
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.
@ -17,10 +13,6 @@ Pull requests should be made to the Dev branch as that is the working branch, ma
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.
## TODO
- [ ] Clean up the ViewModel and DataModel code
- [ ] Migrate to Jetpack Compose when it's officially released
## Building
<div>
@ -43,7 +35,7 @@ chmod +x gradlew
./gradlew assembleDebug
```
## Vanced FAQ
Vanced FAQ (from the faq branch) now available on the playstore!
## Vanced Guide
[Vanced Guide](https://github.com/YTVanced/VancedGuide/) now available on the playstore!
<a href='https://play.google.com/store/apps/details?id=com.vanced.faq&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="85" src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png'/></a>

2
app/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/release

View File

@ -1,41 +1,29 @@
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("com.google.firebase.firebase-perf")
id("androidx.navigation.safeargs.kotlin")
id("kotlin-android")
}
android {
compileSdkVersion(30)
compileSdk = 30
defaultConfig {
minSdk = 21
targetSdk = 30
applicationId = "com.vanced.manager"
minSdkVersion(21)
targetSdkVersion(30)
versionCode = 260
versionName = "2.6.0 (Crimson)"
versionCode = 3000
versionName = "3.0.0 (Re@Composed)"
vectorDrawables.useSupportLibrary = true
buildConfigField("String[]", "MANAGER_LANGUAGES", "{$languages}")
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")}\"")
}
lintOptions {
lint {
disable("MissingTranslation", "ExtraTranslation")
}
applicationVariants.all {
resValue("string", "versionName", versionName)
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
@ -44,30 +32,29 @@ android {
}
buildFeatures {
viewBinding = true
//compose = true
compose = true
}
packagingOptions {
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/*.kotlin_module")
resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/*.kotlin_module")
}
// To inline the bytecode built with JVM target 1.8 into
// bytecode that is being built with JVM target 1.6. (e.g. navArgs)
// To inline the bytecode built with JVM target 1.8 into
// bytecode that is being built with JVM target 1.6. (e.g. navArgs)
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
//useIR = true
}
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
useIR = true
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
}
}
val languages: String get() {
@ -76,7 +63,7 @@ val languages: String get() {
File("$projectDir/src/main/res").listFiles()?.filter {
val name = it.name
name.startsWith("values-") && !name.contains("v23")
name.startsWith("values") && !name.contains("v23") && !name.contains("night")
}?.forEach { dir ->
val dirname = dir.name.substringAfter("-").substringBefore("-")
if (!exceptions.contains(dirname)) {
@ -87,69 +74,49 @@ val languages: String get() {
}
dependencies {
//val composeVersion = "1.0.0-alpha12"
implementation(project(":core-presentation"))
implementation(project(":core-ui"))
implementation(project(":library-network"))
// Kotlin
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
// AndroidX
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.3.3")
implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
// Compose
// implementation("androidx.compose.ui:ui:$composeVersion")
// implementation("androidx.compose.ui:ui-tooling:$composeVersion")
// implementation("androidx.compose.foundation:foundation:$composeVersion")
// implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha02")
// implementation("androidx.compose.material:material:$composeVersion")
// implementation("androidx.compose.material:material-icons-core:$composeVersion")
// implementation("androidx.compose.material:material-icons-extended:$composeVersion")
// implementation("androidx.compose.runtime:runtime-livedata:$composeVersion")
// Appearance
implementation("com.github.madrapps:pikolo:2.0.1")
implementation("androidx.core:core-ktx:1.5.0")
implementation("androidx.appcompat:appcompat:1.3.0")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.browser:browser:1.3.0")
// JSON parser
implementation("com.beust:klaxon:5.5")
val composeVersion = "1.0.0-beta07"
implementation("androidx.compose.ui:ui:$composeVersion")
implementation("androidx.compose.material:material:$composeVersion")
implementation("androidx.compose.ui:ui-tooling:$composeVersion")
implementation("androidx.compose.ui:ui-util:$composeVersion")
implementation("androidx.compose.foundation:foundation:$composeVersion")
implementation("androidx.compose.material:material:$composeVersion")
implementation("androidx.compose.material:material-icons-core:$composeVersion")
implementation("androidx.compose.material:material-icons-extended:$composeVersion")
implementation("androidx.compose.runtime:runtime-livedata:$composeVersion")
// Crowdin
implementation("com.github.crowdin.mobile-sdk-android:sdk:1.4.0")
implementation("androidx.activity:activity-compose:1.3.0-alpha08")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.navigation:navigation-compose:2.4.0-alpha01")
implementation("androidx.preference:preference-ktx:1.1.1")
// HTTP networking
implementation("com.github.kittinunf.fuel:fuel:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-json:2.3.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
val accompanistVersion = "0.10.0"
implementation("com.google.accompanist:accompanist-glide:$accompanistVersion")
implementation("com.google.accompanist:accompanist-swiperefresh:$accompanistVersion")
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
// Root permissions
implementation("com.github.topjohnwu.libsu:core:3.1.2")
implementation("com.github.topjohnwu.libsu:io:3.1.2")
implementation("com.github.madrapps:pikolo:2.0.1")
// Layout
implementation("com.google.android:flexbox:2.0.1")
val koinVersion = "3.0.1"
implementation("io.insert-koin:koin-android:$koinVersion")
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
// Firebase
implementation("com.google.firebase:firebase-analytics-ktx:18.0.3")
implementation("com.google.firebase:firebase-crashlytics:17.4.1")
implementation("com.google.firebase:firebase-messaging:21.1.0")
implementation("com.google.firebase:firebase-perf:19.1.1")
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion")
implementation("dev.burnoo:compose-remember-preference:0.3.0")
implementation("com.github.x1nto:apkhelper:1.1.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.2")
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
}

View File

@ -1,42 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.vanced.manager">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- is required for some Android 5.x devices -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="22"
tools:ignore="ScopedStorage" />
<queries>
<package android:name="com.vanced.android.youtube" />
<package android:name="com.google.android.youtube" />
<package android:name="com.vanced.android.apps.youtube.music" />
<package android:name="com.google.android.apps.youtube.music" />
<package android:name="com.mgoogle.android.gms" />
<package android:name="com.vanced.faq" />
<package android:name="com.android.vending" />
</queries>
<application
android:name=".core.App"
android:name=".ManagerApplication"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:ignore="UnusedAttribute">
android:supportsRtl="true">
<activity
android:name=".ui.SplashScreenActivity"
android:name=".SplashScreenActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/SplashTheme">
<intent-filter>
@ -44,63 +22,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".ui.WelcomeActivity"
android:theme="@style/DarkTheme"/>
<activity
android:name=".ui.MainActivity"
android:configChanges="layoutDirection|locale|keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/DarkTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="api.vancedapp.com"/>
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider" />
</provider>
<service
android:name=".core.firebase.CloudMessaging"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_name" />
<service android:name=".core.installer.AppUninstallerService" />
<service android:name=".core.installer.AppInstallerService" />
android:name=".MainActivity"
android:theme="@style/Theme.MaterialComponents.NoActionBar"
android:label="@string/app_name"/>
</application>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,156 @@
package com.vanced.manager
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIos
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.vanced.manager.preference.defPrefs
import com.vanced.manager.preference.managerTheme
import com.vanced.manager.ui.composables.*
import com.vanced.manager.ui.layouts.*
import com.vanced.manager.ui.screens.Screen
import com.vanced.manager.ui.theme.ComposeTestTheme
import com.vanced.manager.ui.theme.isDark
import com.vanced.manager.ui.theme.managerTheme
class MainActivity : AppCompatActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
managerTheme = defPrefs.managerTheme
ComposeTestTheme {
//ButtonLayoutActivity()
MainActivityLayout()
}
}
}
@ExperimentalAnimationApi
@Composable
fun MainActivityLayout() {
val isMenuExpanded = remember { mutableStateOf(false) }
val systemUiController = rememberSystemUiController()
val surfaceColor = managerSurfaceColor()
val isDark = isDark()
val navController = rememberNavController()
val dropdownScreens = listOf(
Screen.Settings,
Screen.Logs,
Screen.About
)
SideEffect {
systemUiController.setSystemBarsColor(surfaceColor, !isDark)
}
Scaffold(
topBar = {
MainToolbar(
navController,
dropdownScreens,
isMenuExpanded
)
},
backgroundColor = managerSurfaceColor()
) {
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) {
HomeLayout()
}
}
}
}
@Composable
fun MainToolbar(
navController: NavController,
dropdownScreens: List<Screen>,
isMenuExpanded: MutableState<Boolean>
) {
TopAppBar(
title = {
navController.currentDestination
Text(
//This does not look good at all
text = Screen::class.sealedSubclasses.find {
it.objectInstance?.route == navController.currentDestination?.route
}?.objectInstance?.displayName ?: "",
color = managerAnimatedColor(color = MaterialTheme.colors.onSurface)
)
},
backgroundColor = managerAnimatedColor(color = MaterialTheme.colors.surface),
actions = {
if (navController.currentDestination?.route == Screen.Home.route) {
IconButton(
onClick = { isMenuExpanded.value = !isMenuExpanded.value }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = managerTextColor()
)
}
DropdownMenu(
expanded = isMenuExpanded.value,
onDismissRequest = {
isMenuExpanded.value = false
},
modifier = Modifier.background(managerCardColor())
) {
dropdownScreens.forEach {
ManagerDropdownMenuItem(
isMenuExpanded = isMenuExpanded,
title = it.displayName
) {
navController.navigate(it.route) {
popUpTo(Screen.Home.route)
anim {
enter = R.animator.fragment_enter
exit = R.animator.fragment_exit
popEnter = R.animator.fragment_enter_pop
popEnter = R.animator.fragment_exit_pop
}
restoreState = true
}
}
}
}
}
},
navigationIcon = if (navController.currentDestination?.route != Screen.Home.route) { {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBackIos,
contentDescription = null,
tint = managerTextColor()
)
}
}} else null,
elevation = 0.dp
)
}
}

View File

@ -0,0 +1,28 @@
package com.vanced.manager
import android.app.Application
import com.vanced.manager.di.*
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class ManagerApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@ManagerApplication)
modules(
mapperModule,
viewModelModule,
serviceModule,
repositoryModule,
packageManagerModule,
networkModule,
downloaderModule
)
}
}
}

View File

@ -0,0 +1,17 @@
package com.vanced.manager
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
class SplashScreenActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(
Intent(this, MainActivity::class.java)
)
}
}

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(apps[position]).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(
buttonTag,
apps[position]
)
}
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
}
dataModel?.buttonImage?.observe(activity) {
if (it != null) {
appDownload.icon = 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

@ -1,79 +0,0 @@
package com.vanced.manager.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.messaging.FirebaseMessaging
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewNotificationSettingBinding
import com.vanced.manager.model.NotifModel
import com.vanced.manager.utils.defPrefs
class GetNotifAdapter(private val context: Context) :
RecyclerView.Adapter<GetNotifAdapter.GetNotifViewHolder>() {
private val prefs = context.defPrefs
private val vanced = NotifModel(
"Vanced-Update",
context.getString(R.string.push_notifications, context.getString(R.string.vanced)),
context.getString(R.string.push_notifications_summary, context.getString(R.string.vanced)),
"vanced_notifs"
)
private val music = NotifModel(
"Music-Update",
context.getString(R.string.push_notifications, context.getString(R.string.music)),
context.getString(R.string.push_notifications_summary, context.getString(R.string.music)),
"music_notifs"
)
private val microg = NotifModel(
"MicroG-Update",
context.getString(R.string.push_notifications, context.getString(R.string.microg)),
context.getString(R.string.push_notifications_summary, context.getString(R.string.microg)),
"microg_notifs"
)
private val apps = arrayOf(vanced, music, microg)
inner class GetNotifViewHolder(val binding: ViewNotificationSettingBinding) :
RecyclerView.ViewHolder(binding.root) {
val switch = binding.notifSwitch
fun bind(position: Int) {
val app = apps[position]
with(binding.notifSwitch) {
setKey(app.key)
setSummary(app.switchSummary)
setTitle(app.switchTitle)
setDefaultValue(true)
with(prefs) {
setChecked(
getBoolean(
"enable_" + app.key.substringBefore("_"),
true
) && getBoolean(app.key, true)
)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GetNotifViewHolder {
val view =
ViewNotificationSettingBinding.inflate(LayoutInflater.from(context), parent, false)
return GetNotifViewHolder(view)
}
override fun onBindViewHolder(holder: GetNotifViewHolder, position: Int) {
holder.bind(position)
holder.switch.setOnCheckedListener { _, isChecked ->
when (isChecked) {
true -> FirebaseMessaging.getInstance().subscribeToTopic(apps[position].topic)
false -> FirebaseMessaging.getInstance().unsubscribeFromTopic(apps[position].topic)
}
}
}
override fun getItemCount(): Int = 3
}

View File

@ -1,95 +0,0 @@
package com.vanced.manager.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewSocialLinkBinding
import com.vanced.manager.model.LinkModel
import com.vanced.manager.ui.viewmodels.HomeViewModel
class LinkAdapter(
private val context: Context,
private val viewModel: HomeViewModel
) : RecyclerView.Adapter<LinkAdapter.LinkViewHolder>() {
private val instagram = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_instagram),
INSTAGRAM
)
private val youtube = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_youtube),
YOUTUBE
)
private val github = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_github),
GITHUB
)
private val website = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_website),
WEBSITE
)
private val telegram = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_telegram),
TELEGRAM
)
private val twitter = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_twitter),
TWITTER
)
private val discord = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_discord),
DISCORD
)
private val reddit = LinkModel(
AppCompatResources.getDrawable(context, R.drawable.ic_reddit),
REDDIT
)
val links = arrayOf(instagram, youtube, github, website, telegram, twitter, discord, reddit)
inner class LinkViewHolder(private val binding: ViewSocialLinkBinding) :
RecyclerView.ViewHolder(binding.root) {
val logo = binding.linkImage
fun bind(position: Int) {
binding.linkBg.setOnClickListener {
viewModel.openUrl(links[position].linkUrl)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkViewHolder {
val view = ViewSocialLinkBinding.inflate(LayoutInflater.from(context), parent, false)
return LinkViewHolder(view)
}
override fun onBindViewHolder(holder: LinkViewHolder, position: Int) {
holder.bind(position)
holder.logo.setImageDrawable(links[position].linkIcon)
}
override fun getItemCount(): Int = links.size
companion object {
const val INSTAGRAM = "https://instagram.com/vanced.youtube"
const val YOUTUBE = "https://youtube.com/c/YouTubeVanced"
const val GITHUB = "https://github.com/YTVanced/VancedManager"
const val WEBSITE = "https://vancedapp.com"
const val TELEGRAM = "https://t.me/joinchat/AAAAAEHf-pi4jH1SDlAL4w"
const val TWITTER = "https://twitter.com/YTVanced"
const val DISCORD = "https://discord.gg/WCGNdRruzb"
const val REDDIT = "https://www.reddit.com/r/Vanced/"
}
}

View File

@ -1,60 +0,0 @@
package com.vanced.manager.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.recyclerview.widget.RecyclerView
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewAppCheckboxBinding
import com.vanced.manager.model.SelectAppModel
import com.vanced.manager.utils.enableMusic
import com.vanced.manager.utils.enableVanced
class SelectAppsAdapter(private val context: Context) :
RecyclerView.Adapter<SelectAppsAdapter.SelectAppsViewHolder>() {
private val prefs by lazy { getDefaultSharedPreferences(context) }
private val vanced = SelectAppModel(
context.getString(R.string.vanced),
context.getString(R.string.description_vanced),
"vanced",
prefs.enableVanced
)
private val music = SelectAppModel(
context.getString(R.string.music),
context.getString(R.string.description_vanced_music),
"music",
prefs.enableMusic
)
val apps = arrayOf(vanced, music)
inner class SelectAppsViewHolder(binding: ViewAppCheckboxBinding) :
RecyclerView.ViewHolder(binding.root) {
val appName = binding.appCheckboxText
val appDescription = binding.appCheckboxDescription
val appCard = binding.appCheckboxBg
val checkbox = binding.appCheckbox
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectAppsViewHolder {
val view = ViewAppCheckboxBinding.inflate(LayoutInflater.from(context), parent, false)
return SelectAppsViewHolder(view)
}
override fun onBindViewHolder(holder: SelectAppsViewHolder, position: Int) {
holder.appName.text = apps[position].appName
holder.appDescription.text = apps[position].appDescription
holder.checkbox.isChecked = apps[position].isChecked
holder.appCard.setOnClickListener {
holder.checkbox.isChecked = !holder.checkbox.isChecked
apps[position].isChecked = !apps[position].isChecked
}
}
override fun getItemCount(): Int = 2
}

View File

@ -1,68 +0,0 @@
package com.vanced.manager.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewSponsorBinding
import com.vanced.manager.model.SponsorModel
import com.vanced.manager.ui.viewmodels.HomeViewModel
import com.vanced.manager.utils.LIGHT
import com.vanced.manager.utils.currentTheme
class SponsorAdapter(
private val context: Context,
private val viewModel: HomeViewModel
) : RecyclerView.Adapter<SponsorAdapter.LinkViewHolder>() {
private val brave = SponsorModel(
if (currentTheme == LIGHT) AppCompatResources.getDrawable(
context,
R.drawable.ic_brave_light
) else AppCompatResources.getDrawable(context, R.drawable.ic_brave),
"Brave",
BRAVE
)
private val adguard = SponsorModel(
AppCompatResources.getDrawable(context, R.drawable.ic_adguard),
"AdGuard",
ADGUARD
)
val sponsors = arrayListOf(brave, adguard)
inner class LinkViewHolder(private val binding: ViewSponsorBinding) : RecyclerView.ViewHolder(
binding.root
) {
val logo = binding.sponsorLogo
fun bind(position: Int) {
with(binding) {
sponsorName.text = sponsors[position].name
cardSponsor.setOnClickListener {
viewModel.openUrl(sponsors[position].url)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkViewHolder {
val view = ViewSponsorBinding.inflate(LayoutInflater.from(context), parent, false)
return LinkViewHolder(view)
}
override fun onBindViewHolder(holder: LinkViewHolder, position: Int) {
holder.bind(position)
holder.logo.setImageDrawable(sponsors[position].logo)
}
override fun getItemCount(): Int = 2
companion object {
const val BRAVE = "https://vancedapp.com/brave"
const val ADGUARD = "https://adguard.com/?aid=31141&source=manager"
}
}

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,55 +0,0 @@
package com.vanced.manager.core
import android.app.Application
import android.content.res.Configuration
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.crowdin.platform.CrowdinConfig
import com.crowdin.platform.data.model.AuthConfig
import com.crowdin.platform.data.remote.NetworkType
import com.vanced.manager.BuildConfig.*
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.loadJson
import com.vanced.manager.utils.managerAccent
import com.vanced.manager.utils.mutableAccentColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
open class App : Application() {
private val prefs by lazy { getDefaultSharedPreferences(this) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onCreate() {
scope.launch { loadJson(this@App) }
super.onCreate()
mutableAccentColor.value = prefs.managerAccent
Crowdin.init(
this,
CrowdinConfig.Builder().apply {
withDistributionHash(CROWDIN_HASH)
withNetworkType(NetworkType.WIFI)
if (ENABLE_CROWDIN_AUTH) {
if (prefs.getBoolean("crowdin_real_time", false))
withRealTimeUpdates()
withSourceLanguage("en")
withAuthConfig(AuthConfig(CROWDIN_CLIENT_ID, CROWDIN_CLIENT_SECRET, null))
withScreenshotEnabled()
log("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,29 +0,0 @@
package com.vanced.manager.core.downloader
import android.content.Context
import com.vanced.manager.R
import com.vanced.manager.utils.*
import com.vanced.manager.utils.DownloadHelper.download
import com.vanced.manager.utils.PackageHelper.install
object MicrogDownloader {
private const val fileName = "microg.apk"
private const val folderName = "microg"
fun downloadMicrog(context: Context) {
val url = microg.value?.string("url") ?: ""
download(url, "$baseInstallUrl/", folderName, fileName, context, onDownloadComplete = {
startMicrogInstall(context)
}, onError = {
downloadingFile.postValue(context.getString(R.string.error_downloading, fileName))
})
}
fun startMicrogInstall(context: Context) {
installing.postValue(true)
postReset()
install("${context.getExternalFilesDir(folderName)}/$fileName", context)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,29 @@
package com.vanced.manager.di
import com.vanced.manager.downloader.impl.VancedDownloader
import com.vanced.manager.downloader.api.VancedAPI
import com.vanced.manager.network.util.baseUrl
import okhttp3.OkHttpClient
import org.koin.dsl.module
import retrofit2.Retrofit
val downloaderModule = module {
fun provideVancedAPI(
okHttpClient: OkHttpClient
): VancedAPI = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.build()
.create(VancedAPI::class.java)
fun provideVancedDownloader(
vancedAPI: VancedAPI
): VancedDownloader = VancedDownloader(
vancedAPI = vancedAPI
)
single { provideVancedAPI(get()) }
single { provideVancedDownloader(get()) }
}

View File

@ -0,0 +1,28 @@
package com.vanced.manager.di
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.downloader.impl.VancedDownloader
import com.vanced.manager.network.model.AppDtoMapper
import com.vanced.manager.network.model.JsonDtoMapper
import org.koin.dsl.module
val mapperModule = module {
fun provideAppMapper(
packageInformationDataSource: PackageInformationDataSource,
vancedDownloader: VancedDownloader,
): AppDtoMapper = AppDtoMapper(
packageInformationDataSource = packageInformationDataSource,
vancedDownloader = vancedDownloader
)
fun provideJsonMapper(
appDtoMapper: AppDtoMapper
): JsonDtoMapper = JsonDtoMapper(
appDtoMapper = appDtoMapper
)
single { provideAppMapper(get(), get()) }
single { provideJsonMapper(get()) }
}

View File

@ -0,0 +1,12 @@
package com.vanced.manager.di
import okhttp3.OkHttpClient
import org.koin.dsl.module
val networkModule = module {
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
single { provideOkHttpClient() }
}

View File

@ -0,0 +1,30 @@
package com.vanced.manager.di
import android.content.Context
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.domain.datasource.PackageInformationDataSourceImpl
import com.vanced.manager.domain.pkg.PkgManager
import com.vanced.manager.domain.pkg.PkgManagerImpl
import org.koin.dsl.module
val packageManagerModule = module {
fun providePackageManager(
context: Context
): PkgManager {
return PkgManagerImpl(
packageManager = context.packageManager
)
}
fun providePackageInformationDataSource(
pkgManager: PkgManager
): PackageInformationDataSource {
return PackageInformationDataSourceImpl(
pkgManager = pkgManager
)
}
single { providePackageManager(get()) }
single { providePackageInformationDataSource(get()) }
}

View File

@ -0,0 +1,21 @@
package com.vanced.manager.di
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.model.JsonDtoMapper
import com.vanced.manager.repository.JsonRepository
import com.vanced.manager.repository.JsonRepositoryImpl
import org.koin.dsl.module
val repositoryModule = module {
fun provideJsonRepository(
jsonService: JsonService,
jsonDtoMapper: JsonDtoMapper
): JsonRepository = JsonRepositoryImpl(
jsonService,
jsonDtoMapper
)
single { provideJsonRepository(get(), get()) }
}

View File

@ -0,0 +1,23 @@
package com.vanced.manager.di
import com.google.gson.GsonBuilder
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.util.baseGithubUrl
import okhttp3.OkHttpClient
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val serviceModule = module {
fun provideRetrofitService(okHttpClient: OkHttpClient): JsonService =
Retrofit.Builder()
.baseUrl(baseGithubUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(okHttpClient)
.build()
.create(JsonService::class.java)
single { provideRetrofitService(get()) }
}

View File

@ -0,0 +1,9 @@
package com.vanced.manager.di
import com.vanced.manager.ui.viewmodel.HomeViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val viewModelModule = module {
viewModel { HomeViewModel(get(), get()) }
}

View File

@ -1,20 +1,20 @@
package com.vanced.manager.feature.home.data.datasource
package com.vanced.manager.domain.datasource
import android.content.pm.PackageManager
import com.vanced.manager.feature.home.data.pkg.PkgManager
import com.vanced.manager.domain.pkg.PkgManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface PkgInformationDataSource {
interface PackageInformationDataSource {
suspend fun getVersionCode(packageName: String): Int?
suspend fun getVersionName(packageName: String): String?
}
class PkgInformationDataSourceImpl(
class PackageInformationDataSourceImpl(
private val pkgManager: PkgManager
) : PkgInformationDataSource {
) : PackageInformationDataSource {
override suspend fun getVersionCode(packageName: String): Int? =
withContext(Dispatchers.IO) {

View File

@ -0,0 +1,23 @@
package com.vanced.manager.domain.model
import com.vanced.manager.downloader.base.BaseDownloader
data class App(
val name: String? = null,
val remoteVersion: String? = null,
val remoteVersionCode: Int? = null,
val installedVersion: String? = null,
val installedVersionCode: Int? = null,
val installedVersionRoot: String? = null,
val installedVersionCodeRoot: Int? = null,
val iconUrl: String? = "",
val appStatus: AppStatus = AppStatus.Install,
val appStatusRoot: AppStatus = AppStatus.Install,
val packageName: String? = null,
val packageNameRoot: String? = null,
val changelog: String? = null,
val url: String? = null,
val themes: List<String>? = null,
val languages: List<String>? = null,
val downloader: BaseDownloader? = null
)

View File

@ -0,0 +1,9 @@
package com.vanced.manager.domain.model
enum class AppStatus {
Install,
Reinstall,
Update,
}

View File

@ -0,0 +1,9 @@
package com.vanced.manager.domain.model
data class Json(
val isMicrogBroken: Boolean,
val manager: App,
val vanced: App,
val music: App,
val microg: App
)

View File

@ -0,0 +1,5 @@
package com.vanced.manager.domain.model
data class Link(
val title: String
)

View File

@ -1,9 +1,8 @@
package com.vanced.manager.feature.home.data.pkg
package com.vanced.manager.domain.pkg
import android.content.pm.PackageManager
import android.os.Build
interface PkgManager {
@Throws(PackageManager.NameNotFoundException::class)

View File

@ -0,0 +1,9 @@
package com.vanced.manager.domain.util
interface EntityMapper <T, Model> {
suspend fun mapToModel(entity: T): Model
suspend fun mapFromModel(model: Model): T
}

View File

@ -0,0 +1,20 @@
package com.vanced.manager.downloader.api
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Streaming
interface VancedAPI {
@GET("apks/v{version}/{variant}/{type}/{apkName}.apk")
@Streaming
fun getApk(
@Path("version") version: String,
@Path("variant") variant: String,
@Path("type") type: String,
@Path("apkName") apkName: String,
) : Call<ResponseBody>
}

View File

@ -0,0 +1,106 @@
package com.vanced.manager.downloader.base
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.vanced.manager.ui.composables.InstallationOption
import com.vanced.manager.util.log
import okhttp3.ResponseBody
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import retrofit2.Call
import retrofit2.awaitResponse
import java.io.*
abstract class BaseDownloader(
private val appName: String
) : KoinComponent {
var downloadProgress by mutableStateOf(0f)
private set
var downloadFile by mutableStateOf("")
private set
var installing by mutableStateOf(false)
private lateinit var call: Call<ResponseBody>
private val context: Context by inject()
abstract suspend fun download()
abstract val installationOptions: List<InstallationOption>
private val tag = this::class.simpleName!!
suspend fun downloadFile(
call: Call<ResponseBody>,
folderStructure: String,
fileName: String,
onError: (error: String) -> Unit = {},
onComplete: suspend () -> Unit = {},
) {
fun error(errorBody: String) {
downloadFile = "Error downloading $fileName"
onError(errorBody)
log(tag, errorBody)
}
try {
this.call = call
val response = call.awaitResponse()
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
if (writeFile(body, fileName, folderStructure)) {
onComplete()
}
}
} else {
val error = response.errorBody()?.toString()
if (error != null) {
error(error)
log(tag, error)
}
}
} catch (e: Exception) {
error(e.stackTraceToString())
}
downloadProgress = 0f
}
fun cancelDownload() {
call.cancel()
}
private fun writeFile(
body: ResponseBody,
fileName: String,
folderStructure: String
): Boolean {
val file = File("${context.getExternalFilesDir(appName)?.path}/$folderStructure/$fileName")
val inputStream = body.byteStream()
val outputStream = FileOutputStream(file)
return try {
val totalBytes = body.contentLength()
val fileReader = ByteArray(4096)
var downloadedBytes = 0L
var read: Int
while (inputStream.read(fileReader).also { read = it } != -1) {
outputStream.write(fileReader, 0, read)
downloadedBytes += read
downloadProgress = (downloadedBytes * 100 / totalBytes).toFloat()
}
true
} catch (e: IOException) {
false
} finally {
inputStream.close()
outputStream.close()
}
}
}

View File

@ -0,0 +1,15 @@
package com.vanced.manager.downloader.impl
import com.vanced.manager.downloader.base.BaseDownloader
import com.vanced.manager.ui.composables.InstallationOption
object MicrogDownloader : BaseDownloader("") {
override suspend fun download() {
}
override val installationOptions: List<InstallationOption>
get() = TODO("Not yet implemented")
}

View File

@ -0,0 +1,15 @@
package com.vanced.manager.downloader.impl
import com.vanced.manager.downloader.base.BaseDownloader
import com.vanced.manager.ui.composables.InstallationOption
object MusicDownloader : BaseDownloader("") {
override suspend fun download() {
}
override val installationOptions: List<InstallationOption>
get() = TODO("Not yet implemented")
}

View File

@ -0,0 +1,112 @@
package com.vanced.manager.downloader.impl
import com.vanced.manager.downloader.api.VancedAPI
import com.vanced.manager.downloader.base.BaseDownloader
import com.vanced.manager.ui.composables.InstallationOption
import com.vanced.manager.ui.composables.InstallationOptionKey
class VancedDownloader(
private val vancedAPI: VancedAPI
) : BaseDownloader(
appName = "vanced"
) {
private val themeInstallationOption = InstallationOption(
title = "Languages",
descriptionKey = InstallationOptionKey(
keyName = "vanced_languages",
keyDefaultValue = "en"
)
) { show, preference ->
// DialogRadioButtonPreference(
// preferenceTitle = "Language",
// preferenceKey = ,
// defaultValue = ,
// buttons =
// )
}
private val versionInstallationOption = InstallationOption(
title = "Languages",
descriptionKey = InstallationOptionKey(
keyName = "vanced_languages",
keyDefaultValue = "en"
)
) { show, preference ->
// DialogRadioButtonPreference(
// preferenceTitle = "Language",
// preferenceKey = ,
// defaultValue = ,
// buttons =
// )
}
private val languageInstallationOption = InstallationOption(
title = "Languages",
descriptionKey = InstallationOptionKey(
keyName = "vanced_languages",
keyDefaultValue = "en"
)
) { show, preference ->
// DialogRadioButtonPreference(
// preferenceTitle = "Language",
// preferenceKey = ,
// defaultValue = ,
// buttons =
// )
}
override val installationOptions: List<InstallationOption> get() = listOf(
languageInstallationOption
)
override suspend fun download() {
downloadTheme()
}
private suspend fun downloadTheme() {
downloadFile(
vancedAPI.getApk(
version = "16.16.38",
variant = "nonroot",
type = "Theme",
apkName = "black.apk"
),
folderStructure = "vanced/nonroot/v16.16.38",
fileName = "black.apk"
) {
downloadArch()
}
}
private suspend fun downloadArch() {
downloadFile(
vancedAPI.getApk(
version = "16.16.38",
variant = "nonroot",
type = "Arch",
apkName = "split_config.x86.apk"
),
folderStructure = "vanced/nonroot/arch",
fileName = "black.apk"
) {
downloadLanguage()
}
}
private suspend fun downloadLanguage() {
downloadFile(
vancedAPI.getApk(
version = "16.16.38",
variant = "nonroot",
type = "Arch",
apkName = "split_config.x86.apk"
),
folderStructure = "vanced/nonroot/lang",
fileName = "black.apk"
) {
downloadLanguage()
}
}
}

View File

@ -0,0 +1,24 @@
package com.vanced.manager.ext
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
fun <T: Any> Call<T>.enqueue(
onResponse: (call: Call<T>, response: Response<T>) -> Unit,
onFailure: (call: Call<T>, t: Throwable) -> Unit
) {
enqueue(object : Callback<T> {
override fun onResponse(
call: Call<T>,
response: Response<T>
) = onResponse(call, response)
override fun onFailure(
call: Call<T>,
t: Throwable
) = onFailure(call, t)
})
}

View File

@ -0,0 +1,31 @@
package com.vanced.manager.installer
import com.xinto.apkhelper.statusCallback
import com.xinto.apkhelper.statusCallbackBuilder
import com.vanced.manager.ui.viewmodel.HomeViewModel
class AppInstaller(
private val viewModel: HomeViewModel
) {
fun installVanced() {
}
fun installMusic() {
}
fun installMicrog() {
}
init {
statusCallback = statusCallbackBuilder(
onAction = {
viewModel.fetch()
}
)
}
}

View File

@ -1,12 +0,0 @@
package com.vanced.manager.model
import android.graphics.drawable.Drawable
data class AppListModel(
val icon: Drawable?,
val appName: String?,
val remoteVersion: String?,
val installedVersion: String?,
val changelog: String?,
val pkg: String?
)

View File

@ -1,6 +0,0 @@
package com.vanced.manager.model
data class AppVersionsModel(
val version: String,
val value: String
)

View File

@ -1,5 +0,0 @@
package com.vanced.manager.model
enum class ButtonTag {
INSTALL, UPDATE, REINSTALL
}

View File

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

View File

@ -1,8 +0,0 @@
package com.vanced.manager.model
import android.graphics.drawable.Drawable
data class LinkModel(
val linkIcon: Drawable?,
val linkUrl: String
)

View File

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

View File

@ -1,37 +0,0 @@
package com.vanced.manager.model
import android.content.Context
import androidx.annotation.DrawableRes
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import com.beust.klaxon.JsonObject
import com.vanced.manager.utils.PackageHelper
class RootDataModel(
jsonObject: LiveData<JsonObject?>,
context: Context,
lifecycleOwner: LifecycleOwner,
appPkg: String,
appName: String,
appDescription: String,
@DrawableRes appIcon: Int,
//BUG THIS!
//kotlin thinks that this value is null if we use
//private val scriptName: String
//Although it's impossible for it to be null.
//Ironic, isn't it?
private val scriptName: String?
) : DataModel(
jsonObject, context, lifecycleOwner, appPkg, appName, appDescription, appIcon
) {
override fun isAppInstalled(pkg: String): Boolean {
//Adapt to nullable shit
return if (scriptName?.let { PackageHelper.scriptExists(it) } == true) {
super.isAppInstalled(appPkg)
} else {
false
}
}
}

View File

@ -1,8 +0,0 @@
package com.vanced.manager.model
data class SelectAppModel(
val appName: String,
val appDescription: String,
val tag: String,
var isChecked: Boolean
)

View File

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

View File

@ -1,6 +0,0 @@
package com.vanced.manager.model
data class VancedPrefModel(
val name: String,
val value: String
)

View File

@ -0,0 +1,11 @@
package com.vanced.manager.network
import com.vanced.manager.network.model.JsonDto
import retrofit2.http.GET
interface JsonService {
@GET("latest.json")
suspend fun get(): JsonDto
}

View File

@ -0,0 +1,16 @@
package com.vanced.manager.network.model
import com.google.gson.annotations.SerializedName
data class AppDto(
@SerializedName("name") val name: String? = null,
@SerializedName("version") val version: String? = null,
@SerializedName("versionCode") val versionCode: Int? = null,
@SerializedName("changelog") val changelog: String? = null,
@SerializedName("package_name") val packageName: String? = null,
@SerializedName("package_name_root") val packageNameRoot: String? = null,
@SerializedName("icon_url") val iconUrl: String? = null,
@SerializedName("url") val url: String? = null,
@SerializedName("themes") val themes: List<String>? = null,
@SerializedName("languages") val languages: List<String>? = null
)

View File

@ -0,0 +1,81 @@
package com.vanced.manager.network.model
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.domain.model.App
import com.vanced.manager.domain.model.AppStatus
import com.vanced.manager.domain.util.EntityMapper
import com.vanced.manager.downloader.base.BaseDownloader
import com.vanced.manager.downloader.impl.MicrogDownloader
import com.vanced.manager.downloader.impl.MusicDownloader
import com.vanced.manager.downloader.impl.VancedDownloader
import com.vanced.manager.network.util.microgName
import com.vanced.manager.network.util.musicName
import com.vanced.manager.network.util.vancedName
class AppDtoMapper(
private val packageInformationDataSource: PackageInformationDataSource,
private val vancedDownloader: VancedDownloader
) : EntityMapper<AppDto, App> {
override suspend fun mapToModel(entity: AppDto): App =
with (entity) {
val localVersionCode = packageInformationDataSource.getVersionCode(packageName ?: "")
val localVersionCodeRoot = packageInformationDataSource.getVersionCode(packageNameRoot ?: "")
val localVersionName = packageInformationDataSource.getVersionName(packageName ?: "")
val localVersionNameRoot = packageInformationDataSource.getVersionName(packageNameRoot ?: "")
App(
name = name,
remoteVersion = version,
remoteVersionCode = versionCode,
installedVersion = localVersionName,
installedVersionCode = localVersionCode,
installedVersionRoot = localVersionNameRoot,
installedVersionCodeRoot = localVersionCodeRoot,
appStatus = compareVersionCodes(versionCode, localVersionCode),
appStatusRoot = compareVersionCodes(versionCode, localVersionCodeRoot),
packageName = packageName,
packageNameRoot = packageNameRoot,
iconUrl = iconUrl,
changelog = changelog,
url = url,
themes = themes,
languages = languages,
downloader = getDownloader(name)
)
}
override suspend fun mapFromModel(model: App): AppDto =
with (model) {
AppDto(
name = name,
version = remoteVersion,
versionCode = remoteVersionCode,
changelog = changelog,
url = url,
themes = themes,
languages = languages,
packageName = packageName,
packageNameRoot = packageNameRoot,
iconUrl = iconUrl
)
}
private fun compareVersionCodes(remote: Int?, local: Int?): AppStatus =
if (local != null && remote != null) {
when {
remote > local -> AppStatus.Update
remote <= local -> AppStatus.Reinstall
else -> AppStatus.Install
}
} else {
AppStatus.Install
}
private fun getDownloader(app: String?): BaseDownloader? =
when (app) {
vancedName -> vancedDownloader
musicName -> MusicDownloader
microgName -> MicrogDownloader
else -> null
}
}

View File

@ -0,0 +1,11 @@
package com.vanced.manager.network.model
import com.google.gson.annotations.SerializedName
data class JsonDto(
@SerializedName("is_microg_broken") var isMicrogBroken: Boolean,
@SerializedName("manager") var manager: AppDto,
@SerializedName("vanced") var vanced: AppDto,
@SerializedName("music") var music: AppDto,
@SerializedName("microg") var microg: AppDto
)

View File

@ -0,0 +1,32 @@
package com.vanced.manager.network.model
import com.vanced.manager.domain.model.Json
import com.vanced.manager.domain.util.EntityMapper
class JsonDtoMapper(
private val appDtoMapper: AppDtoMapper
) : EntityMapper<JsonDto, Json> {
override suspend fun mapToModel(entity: JsonDto): Json =
with (entity) {
Json(
isMicrogBroken = isMicrogBroken,
manager = appDtoMapper.mapToModel(manager),
vanced = appDtoMapper.mapToModel(vanced),
music = appDtoMapper.mapToModel(music),
microg = appDtoMapper.mapToModel(microg)
)
}
override suspend fun mapFromModel(model: Json): JsonDto =
with (model) {
JsonDto(
isMicrogBroken = isMicrogBroken,
manager = appDtoMapper.mapFromModel(manager),
vanced = appDtoMapper.mapFromModel(vanced),
music = appDtoMapper.mapFromModel(music),
microg = appDtoMapper.mapFromModel(microg)
)
}
}

View File

@ -0,0 +1,8 @@
package com.vanced.manager.network.util
const val baseUrl = "https://api.vancedapp.com/api/v1/"
const val baseGithubUrl = "https://x1nto.github.io/VancedFiles/"
const val vancedName = "YouTube Vanced"
const val musicName = "YouTube Vanced Music"
const val microgName = "Vanced microG"

View File

@ -0,0 +1,16 @@
package com.vanced.manager.preference
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.preference.PreferenceManager
val Context.defPrefs: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(this)
var SharedPreferences.managerTheme
get() = getString("manager_theme", "Light")
set(value) {
edit {
putString("manager_theme", value)
}
}

View File

@ -0,0 +1,9 @@
package com.vanced.manager.repository
import com.vanced.manager.domain.model.Json
interface JsonRepository {
suspend fun fetch(): Json
}

View File

@ -0,0 +1,16 @@
package com.vanced.manager.repository
import com.vanced.manager.domain.model.Json
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.model.JsonDtoMapper
class JsonRepositoryImpl(
private val service: JsonService,
private val mapper: JsonDtoMapper
) : JsonRepository {
override suspend fun fetch(): Json {
return mapper.mapToModel(service.get())
}
}

View File

@ -1,237 +0,0 @@
package com.vanced.manager.ui
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.crowdin.platform.Crowdin
import com.crowdin.platform.LoadingStateListener
import com.google.firebase.messaging.FirebaseMessaging
import com.vanced.manager.BuildConfig.ENABLE_CROWDIN_AUTH
import com.vanced.manager.BuildConfig.VERSION_CODE
import com.vanced.manager.R
import com.vanced.manager.databinding.ActivityMainBinding
import com.vanced.manager.ui.dialogs.DialogContainer
import com.vanced.manager.ui.dialogs.ManagerUpdateDialog
import com.vanced.manager.ui.dialogs.URLChangeDialog
import com.vanced.manager.ui.fragments.HomeFragmentDirections
import com.vanced.manager.ui.fragments.SettingsFragmentDirections
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.currentLocale
import com.vanced.manager.utils.AppUtils.faqpkg
import com.vanced.manager.utils.AppUtils.log
import com.vanced.manager.utils.AppUtils.playStorePkg
import com.vanced.manager.utils.AppUtils.vancedRootPkg
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private val navHost by lazy { findNavController(R.id.nav_host) }
private val loadingObserver = object : LoadingStateListener {
val tag = "VMLocalisation"
override fun onDataChanged() {
log(tag, "Loaded data")
}
override fun onFailure(throwable: Throwable) {
log(tag, "Failed to load data: ${throwable.stackTraceToString()}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
setFinalTheme()
super.onCreate(savedInstanceState)
if (ENABLE_CROWDIN_AUTH)
authCrowdin()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
with(binding) {
setSupportActionBar(toolbar)
toolbar.setupWithNavController(
this@MainActivity.navHost,
AppBarConfiguration(this@MainActivity.navHost.graph)
)
}
navHost.addOnDestinationChangedListener { _, currFrag: NavDestination, _ ->
setDisplayHomeAsUpEnabled(currFrag.id != R.id.home_fragment)
}
initDialogs(intent.getBooleanExtra("firstLaunch", false))
manager.observe(this) {
if (manager.value?.int("versionCode") ?: 0 > VERSION_CODE) {
ManagerUpdateDialog.newInstance(true).show(this)
}
}
}
override fun onBackPressed() {
if (!navHost.popBackStack())
finish()
}
private fun setDisplayHomeAsUpEnabled(isNeeded: Boolean) {
binding.toolbar.navigationIcon = if (isNeeded) ContextCompat.getDrawable(
this,
R.drawable.ic_keyboard_backspace_black_24dp
) else null
}
override fun onPause() {
super.onPause()
Crowdin.unregisterDataLoadingObserver(loadingObserver)
}
override fun onResume() {
setFinalTheme()
super.onResume()
Crowdin.registerDataLoadingObserver(loadingObserver)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.toolbar_about -> {
navHost.navigate(HomeFragmentDirections.toAboutFragment())
true
}
R.id.toolbar_settings -> {
navHost.navigate(HomeFragmentDirections.toSettingsFragment())
true
}
R.id.toolbar_log -> {
navHost.navigate(HomeFragmentDirections.toLogFragment())
true
}
R.id.toolbar_guide -> {
try {
val intent = if (isPackageInstalled(faqpkg, packageManager)) {
Intent().apply {
component = ComponentName(faqpkg, "$faqpkg.ui.MainActivity")
}
} else {
Intent(Intent.ACTION_VIEW).apply {
val uriBuilder = Uri.parse("https://play.google.com/store/apps/details")
.buildUpon()
.appendQueryParameter("id", faqpkg)
.appendQueryParameter("launch", "true")
data = uriBuilder.build()
setPackage(playStorePkg)
}
}
startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
false
}
}
R.id.toolbar_update_manager -> {
ManagerUpdateDialog.newInstance(false)
.show(supportFragmentManager, "manager_update")
true
}
R.id.dev_settings -> {
navHost.navigate(SettingsFragmentDirections.toDevSettingsFragment())
return true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(Crowdin.wrapContext(LanguageContextWrapper.wrap(newBase)))
}
//I have no idea why the fuck is super method deprecated
@Suppress("DEPRECATION")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
onActivityResult(requestCode)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
//update manager language when system language is changed
@Suppress("DEPRECATION")
if (newConfig.locale != currentLocale) {
recreate() //restarting activity in order to recreate viewmodels, otherwise some text won't update
return
}
when (newConfig.orientation) {
Configuration.ORIENTATION_PORTRAIT -> log(
"VMUI",
"screen orientation changed to portrait"
)
Configuration.ORIENTATION_LANDSCAPE -> log(
"VMUI",
"screen orientation changed to landscape"
)
else -> log("VMUI", "screen orientation changed to [REDACTED]")
}
}
override fun recreate() {
//needed for setting language smh
startActivity(Intent(this, this::class.java))
finish()
}
private fun initDialogs(firstLaunch: Boolean) {
val prefs = getDefaultSharedPreferences(this)
val variant = prefs.managerVariant
prefs.getBoolean("show_root_dialog", true)
if (intent?.data != null && intent.dataString?.startsWith("https") == true) {
val urldialog = URLChangeDialog()
val arg = Bundle()
arg.putString("url", intent.dataString)
urldialog.arguments = arg
urldialog.show(this)
}
if (firstLaunch) {
DialogContainer.showSecurityDialog(this)
with(FirebaseMessaging.getInstance()) {
subscribeToTopic("Vanced-Update")
subscribeToTopic("Music-Update")
subscribeToTopic("MicroG-Update")
}
} else {
if (isMiuiOptimizationsEnabled) {
DialogContainer.miuiDialog(this)
}
}
if (!prefs.getBoolean("statement", true)) {
DialogContainer.statementFalse(this)
}
if (variant == "root") {
if (PackageHelper.getPackageVersionName(vancedRootPkg, packageManager) == "14.21.54") {
DialogContainer.basicDialog(
getString(R.string.hold_on),
getString(R.string.magisk_vanced),
this
)
}
}
}
}

View File

@ -1,22 +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
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,86 +0,0 @@
package com.vanced.manager.ui
import android.animation.ValueAnimator
import android.os.Bundle
import android.util.LayoutDirection
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.addListener
import androidx.viewpager2.widget.ViewPager2
import com.vanced.manager.adapter.WelcomePageAdapter
import com.vanced.manager.databinding.ActivityWelcomeBinding
import kotlin.math.abs
class WelcomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityWelcomeBinding
private var isRtl = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding.root)
isRtl = resources.configuration.layoutDirection == LayoutDirection.RTL
binding.welcomeViewpager.apply {
adapter = WelcomePageAdapter(this@WelcomeActivity)
isUserInputEnabled = false
setPageTransformer { page, position ->
page.apply {
val pageWidth = width.toFloat()
//Thank you, fragula dev!
when {
position > 0 && position < 1 -> {
alpha = 1f
translationX = 0f
}
position > -1 && position <= 0 -> {
alpha = 1.0f - abs(position * 0.7f)
translationX = pageWidth.rtlCompat * position / 1.3F
}
}
}
}
}
}
override fun onBackPressed() {
with(binding) {
if (welcomeViewpager.currentItem == 0) {
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

@ -0,0 +1,226 @@
package com.vanced.manager.ui.composables
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Launch
import androidx.compose.material.icons.outlined.Info
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.glide.rememberGlidePainter
import com.vanced.manager.R
import com.vanced.manager.domain.model.App
import com.vanced.manager.downloader.base.BaseDownloader
import com.vanced.manager.ui.theme.managerAccentColor
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AppCard(
app: App,
installationOptions: List<InstallationOption>? = null
) {
val showDownloadDialog = remember { mutableStateOf(false) }
val showAppInfo = remember { mutableStateOf(false) }
val showInstallationOptions = remember { mutableStateOf(false) }
val icon = rememberGlidePainter(
request = app.iconUrl ?: "",
requestBuilder = {
placeholder(R.drawable.ic_app_icon_placeholder)
},
fadeIn = true
)
ManagerCard {
Column {
AppInfoCard(
appName = app.name,
icon = icon
)
AppActionCard(
appInstalledVersion = app.installedVersion,
appRemoteVersion = app.remoteVersion,
showDownloadDialog = showDownloadDialog,
showAppInfo = showAppInfo,
showInstallationOptions = showInstallationOptions
)
if (installationOptions != null) {
AnimatedVisibility(
modifier = Modifier.fillMaxWidth(),
visible = showInstallationOptions.value
) {
installationOptions.forEach {
it.Content()
}
}
}
}
}
if (app.name != null && app.downloader != null) {
AppDownloadDialog(
app = app.name,
downloader = app.downloader,
showDialog = showDownloadDialog
)
}
if (app.name != null && app.changelog != null) {
ChangeLogDialog(
appName = app.name,
changelog = app.changelog,
icon = icon,
show = showAppInfo
)
}
}
@Composable
fun AppInfoCard(
appName: String?,
icon: Painter
) {
ManagerListItem(
title = appName ?: "",
icon = {
Image(
painter = icon,
contentDescription = "",
modifier = Modifier.size(48.dp, 48.dp),
)
},
topPadding = 8.dp,
bottomPadding = 8.dp
)
}
@Composable
fun AppActionCard(
appRemoteVersion: String?,
appInstalledVersion: String?,
showDownloadDialog: MutableState<Boolean>,
showAppInfo: MutableState<Boolean>,
showInstallationOptions: MutableState<Boolean>
) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.managerAccentColor) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 4.dp)
.background(managerAccentColor().copy(alpha = 0.15f)),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.wrapContentWidth(Alignment.Start)
) {
Text(
text = "Latest: ${appRemoteVersion ?: "Unavailable"}",
fontSize = 12.sp
)
Text(
text = "Installed: ${appInstalledVersion ?: "Unavailable"}",
fontSize = 12.sp
)
}
Row(
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.End)
) {
IconButton(icon = Icons.Outlined.Info, contentDescription = "App Info") {
showAppInfo.value = true
}
IconButton(icon = Icons.Default.DeleteForever, contentDescription = "Uninstall") {}
IconButton(icon = Icons.Default.Launch, contentDescription = "Launch") {}
IconButton(icon = Icons.Default.Download, contentDescription = "Install") {
showInstallationOptions.value = true
}
}
}
}
}
@Composable
fun AppDownloadDialog(
app: String,
downloader: BaseDownloader,
showDialog: MutableState<Boolean>
) {
val coroutineScope = rememberCoroutineScope()
val rememberProgress = remember { downloader.downloadProgress }
val rememberFile = remember { downloader.downloadFile }
val rememberInstalling = remember { downloader.installing }
val showProgress = remember { mutableStateOf(false) }
ManagerDialog(title = app, isShown = showDialog, buttons = {
AppDownloadDialogButtons(
showProgress = showProgress,
showDialog = showDialog,
downloader = downloader,
coroutineScope = coroutineScope
)
}) {
AppDownloadDialogProgress(
progress = rememberProgress,
file = rememberFile,
showProgress = showProgress.value,
installing = rememberInstalling
)
}
}
@Composable
fun ChangeLogDialog(
appName: String,
changelog: String,
icon: Painter,
show: MutableState<Boolean>
) {
if (show.value) {
ManagerDialog(
title = "About $appName",
isShown = show,
buttons = {
ManagerThemedButton(
modifier = Modifier.fillMaxWidth(),
onClick = { show.value = false }
) {
Text(text = "Close")
}
}
) {
Image(
painter = icon,
contentDescription = null,
modifier = Modifier
.size(64.dp)
.align(Alignment.CenterHorizontally)
)
HeaderView(
modifier = Modifier.padding(horizontal = 8.dp),
headerName = "Changelog",
headerPadding = 0.dp
) {
Text(
modifier = Modifier.padding(top = 4.dp),
text = changelog,
fontSize = 14.sp
)
}
}
}
}

View File

@ -0,0 +1,105 @@
package com.vanced.manager.ui.composables
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.inset
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import androidx.core.graphics.ColorUtils
//TODO
@Composable
fun HSLColorPicker(
modifier: Modifier = Modifier
) {
val hslColors = floatArrayOf(0f, 1f, 0.5f)
val hueColors = getColors(hslColors, length = 360, index = 0) { it }
val saturationColors = getColors(hslColors, length = 11, index = 1) { it / 10 }
val lightnessColors = getColors(hslColors, length = 11, index = 2) { it / 10 }
val hueCircle by remember { mutableStateOf(Offset(0f, 0f)) }
val saturationCircle by remember { mutableStateOf(Offset(0f, 0f)) }
val lightnessCircle by remember { mutableStateOf(Offset(0f, 0f)) }
Canvas(modifier = modifier
.size(250.dp, 250.dp)
.pointerInput(Unit) {
detectDragGestures { change: PointerInputChange, dragAmount: Offset ->
val (changeX, changeY) = change.position
}
}
) {
colorArc(
brush = Brush.sweepGradient(hueColors),
startAngle = 0f,
sweepAngle = 360f,
circleOffset = hueCircle
)
inset(
inset = 60f
) {
colorArc(
brush = Brush.linearGradient(
colors = saturationColors,
start = Offset.Infinite,
end = Offset.Zero
),
startAngle = 100f,
sweepAngle = 155f,
circleOffset = saturationCircle
)
colorArc(
brush = Brush.linearGradient(lightnessColors),
startAngle = 280f,
sweepAngle = 155f,
circleOffset = lightnessCircle
)
}
}
}
fun DrawScope.colorArc(
brush: Brush,
startAngle: Float,
sweepAngle: Float,
circleOffset: Offset
) {
drawArc(
brush = brush,
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = false,
style = Stroke(width = 15f)
)
inset(circleOffset.x, circleOffset.y) {
drawCircle(
brush = brush,
radius = 50f
)
}
}
fun getColors(
hslColors: FloatArray,
length: Int,
index: Int,
calculate: (Float) -> Float
): List<Color> {
val colorsCopy = hslColors.copyOf()
return List(length) {
colorsCopy[index] = calculate(it.toFloat())
Color(ColorUtils.HSLToColor(colorsCopy))
}
}

View File

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

View File

@ -0,0 +1,111 @@
package com.vanced.manager.ui.composables
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.Icon
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForwardIos
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.vanced.manager.downloader.base.BaseDownloader
import dev.burnoo.compose.rememberpreference.rememberStringPreference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
data class InstallationOptionKey(
val keyName: String,
val keyDefaultValue: String = "",
val keyInitialValue: String = keyDefaultValue
)
data class InstallationOption(
val title: String,
val descriptionKey: InstallationOptionKey,
val dialog: @Composable (show: MutableState<Boolean>, preference: MutableState<String>) -> Unit
) {
@Composable
fun Content() {
val showDialog = remember { mutableStateOf(false) }
val preference = rememberStringPreference(
keyName = descriptionKey.keyName,
defaultValue = descriptionKey.keyDefaultValue,
initialValue = descriptionKey.keyInitialValue
)
ManagerListItem(
modifier = Modifier.clickable {
showDialog.value = true
},
title = title,
description = preference.value,
trailing = {
Icon(imageVector = Icons.Default.ArrowForwardIos, contentDescription = null)
}
)
if (showDialog.value) {
dialog(showDialog, preference)
}
}
}
@Composable
fun AppDownloadDialogButtons(
showProgress: MutableState<Boolean>,
showDialog: MutableState<Boolean>,
downloader: BaseDownloader,
coroutineScope: CoroutineScope
) {
when(showProgress.value) {
true -> ManagerThemedButton(onClick = {
downloader.cancelDownload()
showDialog.value = false
showProgress.value = false
}) {
Text(text = "Cancel")
}
false -> ManagerThemedButton(onClick = {
coroutineScope.launch {
showProgress.value = true
downloader.download()
}
}) {
Text(text = "Download")
}
}
}
@Composable
fun AppDownloadDialogProgress(
progress: Float,
file: String,
showProgress: Boolean,
installing: Boolean
) {
if (showProgress) {
when (installing) {
true -> LinearProgressIndicator(color = managerAccentColor())
false -> LinearProgressIndicator(
progress = progress,
color = managerAccentColor()
)
}
Row {
Text(
modifier = Modifier
.weight(1f)
.wrapContentWidth(Alignment.Start),
text = "Downloading $file"
)
Text(
modifier = Modifier
.weight(1f)
.wrapContentWidth(Alignment.End),
text = "$progress"
)
}
}
}

View File

@ -0,0 +1,140 @@
package com.vanced.manager.ui.composables
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.vanced.manager.ui.theme.managerAccentColor
@ExperimentalStdlibApi
@Composable
fun HeaderCard(
headerName: String,
headerPadding: Dp = 11.dp,
content: @Composable ColumnScope.() -> Unit
) {
ManagerCard(
modifier = Modifier
.fillMaxWidth()
) {
Column {
Spacer(modifier = Modifier.size(height = 4.dp, width = 0.dp))
HeaderView(
headerName = headerName,
content = content,
headerPadding = headerPadding
)
}
}
}
@Composable
fun HeaderView(
modifier: Modifier = Modifier,
headerName: String,
headerPadding: Dp = 11.dp,
content: @Composable ColumnScope.() -> Unit
) {
Column(
modifier = modifier
) {
Header(headerName = headerName, headerPadding = headerPadding)
content()
}
}
@OptIn(ExperimentalStdlibApi::class)
@Composable
fun Header(
headerName: String,
headerPadding: Dp = 11.dp,
) {
Text(
headerName.uppercase(),
letterSpacing = 0.15.em,
color = MaterialTheme.colors.managerAccentColor,
modifier = Modifier.padding(horizontal = headerPadding),
fontWeight = FontWeight.Bold,
fontSize = 13.sp
)
}
@Composable
fun IconButton(
icon: ImageVector,
contentDescription: String,
onClick: () -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val buttonSize = 36.dp
val enabled = true
Box(
modifier = Modifier
.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = 18.dp)
)
.size(buttonSize),
contentAlignment = Alignment.Center
) {
val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled
CompositionLocalProvider(LocalContentAlpha provides contentAlpha) {
Icon(
imageVector = icon,
contentDescription = contentDescription
)
}
}
}
@Composable
fun RadiobuttonItem(
currentSelection: MutableState<String?>,
text: String,
preferenceValue: String,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
currentSelection.value = preferenceValue
}
.padding(horizontal = 8.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = preferenceValue == currentSelection.value,
onClick = {
currentSelection.value = preferenceValue
},
colors = RadioButtonDefaults.colors(
MaterialTheme.colors.managerAccentColor,
Color.LightGray
)
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = text,
color = managerTextColor(),
fontSize = 18.sp
)
}
}

View File

@ -0,0 +1,80 @@
package com.vanced.manager.ui.composables
import android.net.Uri
import androidx.annotation.DrawableRes
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@Composable
fun LinkCard(
@DrawableRes icon: Int,
title: String,
link: String
) {
val context = LocalContext.current
val customTabs = remember { CustomTabsIntent.Builder().build() }
ManagerCard(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.clickable {
customTabs.launchUrl(context, Uri.parse(link))
}
.size(width = 120.dp, height = 100.dp)
) {
CompositionLocalProvider(LocalContentColor provides managerTextColor()) {
Column(
modifier = Modifier.padding(all = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
modifier = Modifier.size(36.dp),
painter = painterResource(id = icon),
contentDescription = null,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = title,
textAlign = TextAlign.Center
)
}
}
}
}
@Composable
fun <T> ScrollableLinkRow(
items: List<T>,
content: @Composable (T) -> Unit
) {
val state = rememberLazyListState()
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = state
) {
itemsIndexed(items) { index, item ->
content(item)
if (index < items.size - 1) {
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}

View File

@ -0,0 +1,229 @@
package com.vanced.manager.ui.composables
import androidx.annotation.StringRes
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import com.vanced.manager.ui.theme.cardColor
import com.vanced.manager.ui.theme.managerAccentColor
import java.util.*
@Composable
fun managerShape() = RoundedCornerShape(12.dp)
@Composable
fun ManagerCard(
modifier: Modifier = Modifier,
shape: Shape = managerShape(),
content: @Composable () -> Unit,
) {
Card(
modifier = modifier,
shape = shape,
backgroundColor = managerCardColor(),
elevation = 0.dp,
content = content
)
}
@Composable
fun ManagerThemedButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
content: @Composable RowScope.() -> Unit
) {
val accentColor = managerAccentColor()
Button(
modifier = modifier,
onClick = onClick,
shape = managerShape(),
colors = ButtonDefaults.buttonColors(
backgroundColor = managerAccentColor()
),
elevation = ButtonDefaults.elevation(0.dp)
) {
CompositionLocalProvider(LocalContentColor provides if (accentColor.luminance() > 0.7) Color.Black else Color.White) {
content()
}
}
}
@Composable
fun ManagerDialog(
title: String,
isShown: MutableState<Boolean>,
buttons: @Composable ColumnScope.() -> Unit,
content: @Composable ColumnScope.() -> Unit,
) {
Dialog(
onDismissRequest = { isShown.value = false },
content = {
ManagerCard {
Column(
modifier = Modifier.padding(all = 8.dp)
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = title,
letterSpacing = 2.sp,
fontWeight = FontWeight.Bold,
color = managerTextColor().copy(alpha = 0.8f),
fontSize = 24.sp,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(8.dp))
content()
Spacer(Modifier.height(8.dp))
buttons()
}
}
}
)
}
@Composable
fun ManagerSurface(
content: @Composable () -> Unit
) {
Surface(
modifier = Modifier.fillMaxSize(),
color = managerSurfaceColor(),
content = content
)
}
@Composable
fun ManagerLazyColumn(
content: LazyListScope.() -> Unit
) {
LazyColumn(
contentPadding = PaddingValues(12.dp),
content = content
)
}
@Composable
fun ManagerScrollableColumn(
content: @Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState)
.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
content = content
)
}
@Composable
fun ManagerDropdownMenuItem(
isMenuExpanded: MutableState<Boolean>,
title: String,
onClick: () -> Unit
) {
DropdownMenuItem(
onClick = {
isMenuExpanded.value = false
onClick()
},
) {
Text(text = title)
}
}
@Composable
fun animateManagerColor(
color: Color
): State<Color> {
return animateColorAsState(
targetValue = color,
animationSpec = tween(500)
)
}
@Composable
fun HomeHeaderView(
modifier: Modifier = Modifier,
headerName: String,
content: @Composable () -> Unit
) {
HeaderView(
modifier = modifier,
headerName = headerName,
headerPadding = 4.dp
) {
ManagerHomeHeaderSeparator()
content()
}
}
@Composable
fun managerAnimatedColor(
color: Color
): Color {
val animColor by animateColorAsState(
targetValue = color,
animationSpec = tween(500)
)
return animColor
}
@Composable
fun managerAnimatedText(
@StringRes stringId: Int
): String {
var text by remember { mutableStateOf("") }
CompositionLocalProvider(
LocalConfiguration provides LocalConfiguration.current.apply {
setLocale(Locale("ru"))
}
) {
text = stringResource(id = stringId)
}
return text
}
@Composable
fun ManagerCardSeparator() {
Spacer(modifier = Modifier.height(8.dp))
}
@Composable
fun ManagerHomeHeaderSeparator() {
Spacer(modifier = Modifier.height(4.dp))
}
@Composable
fun managerTextColor(): Color = managerAnimatedColor(color = MaterialTheme.colors.onSurface)
@Composable
fun managerSurfaceColor(): Color = managerAnimatedColor(color = MaterialTheme.colors.surface)
@Composable
fun managerCardColor(): Color = managerAnimatedColor(color = MaterialTheme.colors.cardColor)
@Composable
fun managerAccentColor(): Color = MaterialTheme.colors.managerAccentColor
@Composable
fun animatedManagerAccentColor(): Color = managerAnimatedColor(color = MaterialTheme.colors.managerAccentColor)

View File

@ -0,0 +1,95 @@
package com.vanced.manager.ui.composables
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
const val defaultPadding = 12
@Composable
fun ManagerListItem(
modifier: Modifier = Modifier,
title: String,
description: String? = null,
startPadding: Dp = defaultPadding.dp,
endPadding: Dp = defaultPadding.dp,
topPadding: Dp = defaultPadding.dp,
bottomPadding: Dp = defaultPadding.dp,
icon: @Composable (() -> Unit)? = null,
trailing: @Composable (() -> Unit)? = null
) {
val typography = MaterialTheme.typography
val textColor by animateManagerColor(color = MaterialTheme.colors.onBackground)
Box(
modifier = modifier
) {
Row(
modifier = Modifier
.padding(top = topPadding, bottom = bottomPadding)
) {
if (icon != null) {
Box(
modifier = Modifier
.padding(start = startPadding)
.align(Alignment.CenterVertically)
) {
icon()
}
}
Column(
modifier = Modifier
.padding(
start = startPadding,
end = endPadding
)
.weight(1f)
.align(Alignment.CenterVertically)
) {
if (description == null) {
Spacer(modifier = Modifier.size(6.dp))
}
Text(
text = title,
fontSize = 16.sp,
modifier = Modifier.fillMaxWidth(),
style = typography.subtitle1,
color = textColor
)
if (description != null) {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.medium,
LocalContentColor provides textColor
) {
Text(
text = description,
fontSize = 12.sp,
modifier = Modifier.fillMaxWidth()
)
}
}
if (description == null) {
Spacer(modifier = Modifier.size(6.dp))
}
}
if (trailing != null) {
Box(
modifier = Modifier
.padding(end = endPadding)
.align(Alignment.CenterVertically)
) {
CompositionLocalProvider(LocalContentColor provides textColor) {
trailing()
}
}
}
}
}
}

View File

@ -0,0 +1,148 @@
package com.vanced.manager.ui.composables
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
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.unit.dp
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.vanced.manager.ui.preferences.RadioButtonPreference
@Composable
fun SwitchPreference(
preferenceTitle: String,
preferenceDescription: String?,
preferenceKey: String,
defaultValue: Boolean = true,
onCheckedChange: (isChecked: Boolean) -> Unit = {}
) {
val prefs = PreferenceManager.getDefaultSharedPreferences(LocalContext.current)
var isChecked by remember { mutableStateOf(prefs.getBoolean(preferenceKey, defaultValue)) }
fun savePreference() {
isChecked = !isChecked
prefs.edit {
putBoolean(preferenceKey, isChecked)
}
onCheckedChange(isChecked)
}
Preference(
preferenceTitle = preferenceTitle,
preferenceDescription = preferenceDescription,
onClick = { savePreference() },
trailing = {
Switch(
checked = isChecked,
onCheckedChange = { savePreference() },
colors = SwitchDefaults.colors(
checkedThumbColor = managerAccentColor(),
checkedTrackColor = managerAccentColor(),
uncheckedThumbColor = Color.Gray,
uncheckedTrackColor = Color.Gray
)
)
}
)
}
@Composable
fun Preference(
preferenceTitle: String,
preferenceDescription: String? = null,
trailing: @Composable (() -> Unit)? = null,
onClick: () -> Unit
) {
ManagerListItem(
modifier = Modifier.clickable(onClick = onClick),
title = preferenceTitle,
description = preferenceDescription,
trailing = trailing,
bottomPadding = 4.dp,
topPadding = 4.dp
)
}
@Composable
fun DialogPreference(
preferenceTitle: String,
preferenceDescription: String? = null,
trailing: @Composable (() -> Unit)? = null,
buttons: @Composable ColumnScope.(isShown: MutableState<Boolean>) -> Unit,
content: @Composable ColumnScope.() -> Unit
) {
val isShown = remember { mutableStateOf(false) }
Preference(
preferenceTitle = preferenceTitle,
preferenceDescription = preferenceDescription,
trailing = trailing
) {
isShown.value = true
}
if (isShown.value) {
ManagerDialog(
title = preferenceTitle,
isShown = isShown,
buttons = { buttons(isShown) },
content = content
)
}
}
@Composable
fun DialogRadioButtonPreference(
preferenceTitle: String,
preferenceKey: String,
defaultValue: String,
preferenceDescription: String? = null,
trailing: @Composable (() -> Unit)? = null,
buttons: List<RadioButtonPreference>,
onSave: (newPref: String?) -> Unit = {}
) {
val prefs = PreferenceManager.getDefaultSharedPreferences(LocalContext.current)
val currentSelection = remember { mutableStateOf(prefs.getString(preferenceKey, defaultValue)) }
DialogPreference(
preferenceTitle = preferenceTitle,
preferenceDescription = preferenceDescription,
trailing = trailing,
buttons = { isShown ->
ManagerThemedButton(
modifier = Modifier.fillMaxWidth(),
onClick = {
prefs.edit {
putString(preferenceKey, currentSelection.value)
}
onSave(currentSelection.value)
isShown.value = false
}
) {
Text(text = "Save")
}
}
) {
LazyColumn(
modifier = Modifier
.weight(
weight = 1f,
fill = false
)
) {
items(buttons) { button ->
val (title, key) = button
RadiobuttonItem(
currentSelection = currentSelection,
text = title,
preferenceValue = key
)
}
}
}
}

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

@ -1,61 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewPreferenceBinding
class EmptyPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
private var _binding: ViewPreferenceBinding? = null
val binding: ViewPreferenceBinding
get() = requireNotNull(_binding)
init {
_binding = ViewPreferenceBinding.inflate(LayoutInflater.from(context), this, true)
initAttrs(context, attrs)
}
fun setTitle(newTitle: String) {
binding.preferenceTitle.text = newTitle
}
fun setSummary(newSummary: String) {
with(binding) {
preferenceSummary.text = newSummary
preferenceSummary.isVisible = true
preferenceTitle.setPadding(0, 0, 0, 0)
}
}
private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs?.let { mAttrs ->
val typedArray =
context.obtainStyledAttributes(mAttrs, R.styleable.EmptyPreference, 0, 0)
val title = typedArray.getText(R.styleable.EmptyPreference_preference_title)
val summary = typedArray.getText(R.styleable.EmptyPreference_preference_summary)
with(binding) {
if (summary != null) {
preferenceSummary.text = summary
} else {
preferenceSummary.isGone = true
preferenceTitle.setPadding(0, 12, 0, 12)
}
preferenceTitle.text = title
}
typedArray.recycle()
}
}
}

View File

@ -1,39 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewPreferenceCategoryBinding
class PreferenceCategory @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {
private var _binding: ViewPreferenceCategoryBinding? = null
val binding: ViewPreferenceCategoryBinding
get() = requireNotNull(_binding)
init {
_binding = ViewPreferenceCategoryBinding.inflate(LayoutInflater.from(context), this, true)
initAttrs(context, attrs)
setPadding(0, 4, 0, 0)
orientation = VERTICAL
}
private fun initAttrs(context: Context, attrs: AttributeSet?) {
attrs.let { mAttrs ->
val typedArray =
context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceCategory, 0, 0)
val title = typedArray.getText(R.styleable.PreferenceCategory_category_title)
binding.categoryTitle.text = title
typedArray.recycle()
}
}
}

View File

@ -1,101 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.CompoundButton
import android.widget.FrameLayout
import androidx.core.content.edit
import com.vanced.manager.R
import com.vanced.manager.databinding.ViewPreferenceSwitchBinding
import com.vanced.manager.utils.defPrefs
class PreferenceSwitch @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
fun interface OnCheckedListener {
fun onChecked(buttonView: CompoundButton, isChecked: Boolean)
}
private val prefs by lazy { context.defPrefs }
var prefKey: String = ""
private set
var defValue: Boolean = false
private set
private var mListener: OnCheckedListener? = null
private val prefListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == prefKey) {
binding.preferenceSwitch.isChecked = sharedPreferences.getBoolean(key, defValue)
}
}
private var _binding: ViewPreferenceSwitchBinding? = null
val binding: ViewPreferenceSwitchBinding
get() = requireNotNull(_binding)
init {
_binding = ViewPreferenceSwitchBinding.inflate(LayoutInflater.from(context), this, true)
prefs.registerOnSharedPreferenceChangeListener(prefListener)
attrs?.let { mAttrs ->
with(context.obtainStyledAttributes(mAttrs, R.styleable.PreferenceSwitch, 0, 0)) {
val title = getText(R.styleable.PreferenceSwitch_switch_title)
val summary = getText(R.styleable.PreferenceSwitch_switch_summary)
val key = getText(R.styleable.PreferenceSwitch_switch_key)
val defValue = getBoolean(R.styleable.PreferenceSwitch_switch_def_value, false)
setKey(key)
setDefaultValue(defValue)
setTitle(title)
setSummary(summary)
recycle()
}
}
}
override fun onFinishInflate() {
super.onFinishInflate()
setOnClickListener {
binding.preferenceSwitch.isChecked = !binding.preferenceSwitch.isChecked
}
binding.preferenceSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
prefs.edit { putBoolean(prefKey, isChecked) }
mListener?.onChecked(buttonView, isChecked)
}
}
fun setOnCheckedListener(listener: OnCheckedListener) {
mListener = listener
}
fun setTitle(title: CharSequence?) {
binding.preferenceSwitchTitle.text = title
}
fun setSummary(summary: CharSequence?) {
binding.preferenceSwitchSummary.text = summary
}
fun setKey(key: CharSequence?) {
prefKey = key.toString()
binding.preferenceSwitch.isChecked = prefs.getBoolean(key.toString(), defValue)
}
fun setDefaultValue(newVal: Boolean) {
defValue = newVal
binding.preferenceSwitch.isChecked = prefs.getBoolean(prefKey, newVal)
}
fun setChecked(checked: Boolean) {
binding.preferenceSwitch.isChecked = checked
}
}

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

@ -1,34 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.ColorUtils
import com.google.android.material.button.MaterialButton
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.lifecycleOwner
class ThemedMaterialButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialButton(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
accentColor.observe(owner) { color ->
setBgColor(color.toInt())
}
}
}
private fun setBgColor(color: Int) {
setBackgroundColor(color)
if (ColorUtils.calculateLuminance(color) < 0.7) {
setTextColor(ResourcesCompat.getColor(resources, R.color.White, null))
} else {
setTextColor(ResourcesCompat.getColor(resources, R.color.Black, null))
}
}
}

View File

@ -1,17 +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.checkbox.MaterialCheckBox
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
class ThemedMaterialCheckbox @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : MaterialCheckBox(context, attributeSet, R.attr.checkboxStyle) {
init {
buttonTintList = ColorStateList.valueOf(accentColor.value!!)
}
}

View File

@ -1,17 +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.radiobutton.MaterialRadioButton
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
class ThemedMaterialRadioButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : MaterialRadioButton(context, attributeSet, R.attr.radioButtonStyle) {
init {
buttonTintList = ColorStateList.valueOf(accentColor.value!!)
}
}

View File

@ -1,23 +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.slider.Slider
import com.vanced.manager.utils.accentColor
class ThemedMaterialSlider @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : Slider(context, attributeSet, defStyleAttr) {
init {
val accentValue = ColorStateList.valueOf(accentColor.value!!)
thumbTintList = accentValue
trackActiveTintList = accentValue
trackInactiveTintList = accentValue.withAlpha(70)
haloTintList = accentValue.withAlpha(60)
}
}

View File

@ -1,31 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import androidx.core.graphics.ColorUtils
import com.google.android.material.button.MaterialButton
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.lifecycleOwner
class ThemedOutlinedMaterialButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialButton(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
accentColor.observe(owner) { color ->
applyAccent(color.toInt())
}
}
}
private fun applyAccent(color: Int) {
setTextColor(color)
rippleColor = ColorStateList(
arrayOf(intArrayOf()),
intArrayOf(ColorUtils.setAlphaComponent(color, 50))
)
}
}

View File

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

View File

@ -1,42 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import androidx.appcompat.widget.SwitchCompat
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.DrawableCompat
import com.vanced.manager.R
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.lifecycleOwner
class ThemedSwitchCompat @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : SwitchCompat(context, attributeSet, R.attr.switchStyle) {
private val states =
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked))
init {
context.lifecycleOwner?.let { owner ->
accentColor.observe(owner) { color ->
setSwitchColors(color.toInt())
}
}
}
private fun setSwitchColors(color: Int) {
val thumbColors = intArrayOf(Color.LTGRAY, color)
val trackColors = intArrayOf(Color.GRAY, ColorUtils.setAlphaComponent(color, 70))
DrawableCompat.setTintList(
DrawableCompat.wrap(thumbDrawable),
ColorStateList(states, thumbColors)
)
DrawableCompat.setTintList(
DrawableCompat.wrap(trackDrawable),
ColorStateList(states, trackColors)
)
}
}

View File

@ -1,21 +0,0 @@
package com.vanced.manager.ui.core
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.vanced.manager.utils.accentColor
import com.vanced.manager.utils.lifecycleOwner
class ThemedTextView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attributeSet, defStyleAttr) {
init {
context.lifecycleOwner?.let { owner ->
accentColor.observe(owner) { color ->
setTextColor(color.toInt())
}
}
}
}

View File

@ -1,125 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.vanced.manager.R
import com.vanced.manager.core.downloader.MicrogDownloader.downloadMicrog
import com.vanced.manager.core.downloader.MusicDownloader.downloadMusic
import com.vanced.manager.core.downloader.VancedDownloader.downloadVanced
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppDownloadBinding
import com.vanced.manager.utils.*
class AppDownloadDialog : BindingDialogFragment<DialogAppDownloadBinding>() {
companion object {
const val CLOSE_DIALOG = "close_dialog"
private const val TAG_APP = "TAG_APP"
private const val TAG_VERSION = "TAG_VERSION"
private const val TAG_INSTALLING = "TAG_INSTALLING"
fun newInstance(
app: String,
version: String? = null,
installing: Boolean = false
): AppDownloadDialog = AppDownloadDialog().apply {
arguments = Bundle().apply {
putString(TAG_APP, app)
putString(TAG_VERSION, version)
putBoolean(TAG_INSTALLING, installing)
}
}
}
private val localBroadcastManager by lazy { LocalBroadcastManager.getInstance(requireActivity()) }
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == CLOSE_DIALOG) {
dismiss()
}
}
}
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogAppDownloadBinding.inflate(inflater, container, false)
override fun otherSetups() {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
}
private fun bindData() {
with(binding) {
isCancelable = false
binding.appDownloadProgressbar.applyAccent()
binding.appInstallProgressbar.applyAccent()
bindDownloadProgress()
val app = arguments?.getString(TAG_APP)
appDownloadHeader.text = app
if (arguments?.getBoolean(TAG_INSTALLING) == false) {
when (app) {
getString(R.string.vanced) -> downloadVanced(
requireContext(),
arguments?.getString(TAG_VERSION)
)
getString(R.string.music) -> downloadMusic(
requireContext(),
arguments?.getString(TAG_VERSION)
)
getString(R.string.microg) -> downloadMicrog(requireContext())
}
}
}
}
@SuppressLint("SetTextI18n")
private fun DialogAppDownloadBinding.bindDownloadProgress() {
downloadProgress.observe(viewLifecycleOwner) {
appDownloadProgressbar.progress = it
appDownloadProgress.text = "$it%"
}
installing.observe(viewLifecycleOwner) { installing ->
appDownloadProgressbarContainer.isVisible = !installing
appInstallProgressbar.isVisible = installing
appDownloadFile.isVisible = !installing
appDownloadCancel.isEnabled = !installing
appDownloadCancel.setOnClickListener {
if (installing) {
return@setOnClickListener
}
currentDownload?.cancel()
downloadProgress.value = 0
dismiss()
}
}
downloadingFile.observe(viewLifecycleOwner) {
appDownloadFile.text = it
}
}
override fun onResume() {
super.onResume()
registerReceiver()
}
private fun registerReceiver() {
val intentFilter = IntentFilter()
intentFilter.addAction(CLOSE_DIALOG)
localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter)
}
}

View File

@ -1,54 +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 androidx.annotation.DrawableRes
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogAppInfoBinding
class AppInfoDialog : BindingDialogFragment<DialogAppInfoBinding>() {
companion object {
private const val TAG_APP_NAME = "TAG_APP_NAME"
private const val TAG_APP_ICON = "TAG_APP_ICON"
private const val TAG_CHANGELOG = "TAG_CHANGELOG"
fun newInstance(
appName: String?,
@DrawableRes appIcon: Int?,
changelog: String?
): AppInfoDialog = AppInfoDialog().apply {
arguments = Bundle().apply {
putString(TAG_APP_NAME, appName)
putString(TAG_CHANGELOG, changelog)
if (appIcon != null) {
putInt(TAG_APP_ICON, appIcon)
}
}
}
}
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogAppInfoBinding.inflate(inflater, container, false)
override fun otherSetups() {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
}
private fun bindData() {
with(binding) {
aboutAppName.text = getString(R.string.about_app, arguments?.getString(TAG_APP_NAME))
aboutAppChangelog.text = arguments?.getString(TAG_CHANGELOG)
arguments?.getInt(TAG_APP_ICON)?.let { aboutAppImage.setImageResource(it) }
}
}
}

View File

@ -1,50 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.app.Activity
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
class AppUninstallDialog : BindingDialogFragment<DialogAppUninstallBinding>() {
companion object {
private var TAG_APP_NAME: String? = null
fun newInstance(appName: String?) : AppUninstallDialog = AppUninstallDialog().apply {
arguments = Bundle().apply {
TAG_APP_NAME = appName
}
}
}
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() {
with(binding) {
appUninstallConfirm.setOnClickListener {
//uninstall instruction ??
//dataModel?.appPkg?.let { it1 -> viewModel.uninstallPackage(it1) } (taken from original ExpandanbleAppListAdapter.kt)
//but uninstallPackage method is not static so I would be forced to spawn a new HomeViewModel instance
}
appUninstallCancel.setOnClickListener {
dismiss()
}
appUninstallMessage.text = getString(R.string.uninstall_app_text, TAG_APP_NAME)
}
}
}

View File

@ -1,94 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.edit
import com.google.android.material.radiobutton.MaterialRadioButton
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogBottomRadioButtonBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.formatVersion
class AppVersionSelectorDialog :
BindingBottomSheetDialogFragment<DialogBottomRadioButtonBinding>() {
private val prefs by lazy { requireActivity().defPrefs }
companion object {
private const val TAG_VERSIONS = "TAG_VERSIONS"
private const val TAG_APP = "TAG_APP"
fun newInstance(
versions: List<String>?,
app: String
): AppVersionSelectorDialog = AppVersionSelectorDialog().apply {
arguments = Bundle().apply {
val arrayList = arrayListOf<String>()
versions?.let { arrayList.addAll(it) }
putStringArrayList(TAG_VERSIONS, arrayList)
putString(TAG_APP, app)
}
}
}
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogBottomRadioButtonBinding.inflate(inflater, container, false)
override fun otherSetups() {
bindData()
}
private fun bindData() {
with(binding) {
loadBoxes()?.forEach { mrb ->
dialogRadiogroup.addView(
mrb,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
val tag = root.findViewWithTag<MaterialRadioButton>(
prefs.getString("${arguments?.getString(TAG_APP)}_version", "latest")
)
if (tag != null) {
tag.isChecked = true
}
dialogTitle.text = getString(R.string.version)
dialogSave.setOnClickListener {
val checkedTag = dialogRadiogroup.checkedButtonTag
if (checkedTag != null) {
prefs.edit { putString("${arguments?.getString(TAG_APP)}_version", checkedTag) }
}
dismiss()
}
}
}
private fun loadBoxes() =
arguments?.getStringArrayList(TAG_VERSIONS)?.map { version ->
ThemedMaterialRadioButton(requireActivity()).apply {
text = version.formatVersion(requireActivity())
tag = version
textSize = 18f
}
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
if (arguments?.getString(TAG_APP) == "vanced") {
showDialog(VancedPreferencesDialog())
} else {
showDialog(MusicPreferencesDialog())
}
}
}

View File

@ -1,132 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R
import com.vanced.manager.utils.isMiuiOptimizationsEnabled
import com.vanced.manager.utils.openUrl
import com.vanced.manager.utils.showWithAccent
object DialogContainer {
fun showSecurityDialog(context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.resources.getString(R.string.welcome))
setMessage(context.resources.getString(R.string.security_context))
setPositiveButton(context.resources.getString(R.string.close)) { dialog, _ ->
dialog.cancel()
}
setOnCancelListener {
if (context.isMiuiOptimizationsEnabled) {
miuiDialog(context)
}
}
create()
showWithAccent()
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit { putBoolean("firstLaunch", false) }
}
fun miuiDialog(context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.miui_one_title))
setMessage(context.getString(R.string.miui_one))
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl(
"https://telegra.ph/How-to-install-v15-on-MIUI-02-11",
R.color.Telegram,
context
)
}
setCancelable(false)
create()
showWithAccent()
}
}
fun statementFalse(context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle("Wait what?")
setMessage("So this statement is false huh? I'll go with True!")
setPositiveButton("wut?") { dialog, _ -> dialog.dismiss() }
create()
showWithAccent()
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit { putBoolean("statement", true) }
}
fun installAlertBuilder(msg: String, fullMsg: String?, context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.error))
setMessage(msg)
when (msg) {
context.getString(R.string.installation_signature) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl(
"https://lmgtfy.com/?q=andnixsh+apk+verification+disable",
R.color.Twitter,
context
)
}
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
}
context.getString(R.string.installation_miui) -> {
setPositiveButton(context.getString(R.string.guide)) { _, _ ->
openUrl(
"https://telegra.ph/How-to-install-v15-on-MIUI-02-11",
R.color.Telegram,
context
)
}
setNeutralButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
}
else -> {
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
if (fullMsg != null)
setNegativeButton(context.getString(R.string.advanced)) { _, _ ->
basicDialog(
context.getString(R.string.advanced),
fullMsg,
context
)
}
}
}
create()
showWithAccent()
}
}
fun basicDialog(title: String, msg: String, context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(title)
setMessage(msg)
setPositiveButton(context.getString(R.string.close)) { dialog, _ -> dialog.dismiss() }
create()
showWithAccent()
}
}
}

View File

@ -1,76 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import com.vanced.manager.R
import com.vanced.manager.core.downloader.MicrogDownloader.startMicrogInstall
import com.vanced.manager.core.downloader.MusicDownloader.startMusicInstall
import com.vanced.manager.core.downloader.VancedDownloader.startVancedInstall
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.core.ui.ext.showDialog
import com.vanced.manager.databinding.DialogInstallationFilesDetectedBinding
import com.vanced.manager.utils.defPrefs
import com.vanced.manager.utils.managerVariant
class InstallationFilesDetectedDialog :
BindingBottomSheetDialogFragment<DialogInstallationFilesDetectedBinding>() {
companion object {
private const val TAG_APP = "TAG_APP"
fun newInstance(
app: String
): InstallationFilesDetectedDialog = InstallationFilesDetectedDialog().apply {
arguments = Bundle().apply {
putString(TAG_APP, app)
}
}
}
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogInstallationFilesDetectedBinding.inflate(inflater, container, false)
override fun otherSetups() {
bindData()
}
private fun bindData() {
with(binding) {
val app =
arguments?.getString(TAG_APP) ?: throw IllegalArgumentException("app name is null")
installationDetectedTitle.text = getString(R.string.app_install_files_detected, app)
installationDetectedSummary.text =
getString(R.string.app_install_files_detected_summary, app)
installationDetectedRedownload.setOnClickListener {
dismiss()
when (app) {
getString(R.string.vanced) -> showDialog(VancedPreferencesDialog())
getString(R.string.music) -> showDialog(MusicPreferencesDialog())
else -> showDialog(AppDownloadDialog.newInstance(app))
}
}
installationDetectedInstall.setOnClickListener {
dismiss()
when (app) {
getString(R.string.vanced) -> startVancedInstall(
requireContext(),
context?.defPrefs?.managerVariant
)
getString(R.string.music) -> startMusicInstall(requireContext())
getString(R.string.microg) -> startMicrogInstall(requireContext())
}
showDialog(
AppDownloadDialog.newInstance(
app = app,
installing = true
)
)
}
}
}
}

View File

@ -1,128 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.madrapps.pikolo.listeners.OnColorSelectionListener
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingDialogFragment
import com.vanced.manager.databinding.DialogManagerAccentColorBinding
import com.vanced.manager.utils.*
import com.vanced.manager.utils.AppUtils.log
class ManagerAccentColorDialog : BindingDialogFragment<DialogManagerAccentColorBinding>() {
companion object {
fun newInstance(): ManagerAccentColorDialog = ManagerAccentColorDialog().apply {
arguments = Bundle()
}
}
private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) }
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogManagerAccentColorBinding.inflate(inflater, container, false)
override fun otherSetups() {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bindData()
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
mutableAccentColor.value = prefs.getInt("manager_accent_color", defAccentColor)
}
private fun bindData() {
with(binding) {
val accent = prefs.getInt("manager_accent_color", defAccentColor)
hexEdittext.apply {
setText(accent.toHex(), TextView.BufferType.EDITABLE)
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
if (length() == 0) {
setText("#")
setSelection(1)
}
if (accentColor.value?.toHex() != text.toString() && length() == 7) {
try {
val colorFromEditText = Color.parseColor(text.toString())
accentPicker.setColor(colorFromEditText)
mutableAccentColor.value = colorFromEditText
} catch (e: IllegalArgumentException) {
}
}
}
override fun afterTextChanged(s: Editable?) {}
})
}
accentPicker.apply {
setColor(accent)
setColorSelectionListener(object : OnColorSelectionListener {
override fun onColorSelected(color: Int) {
mutableAccentColor.value = color
hexEdittext.setText(color.toHex(), TextView.BufferType.EDITABLE)
}
override fun onColorSelectionEnd(color: Int) {}
override fun onColorSelectionStart(color: Int) {}
})
}
accentCancel.setOnClickListener {
mutableAccentColor.value = accent
dismiss()
}
accentSave.setOnClickListener {
try {
val colorFromEditText = Color.parseColor(hexEdittext.text.toString())
mutableAccentColor.value = colorFromEditText
prefs.managerAccent = colorFromEditText
} catch (e: IllegalArgumentException) {
log("VMTheme", getString(R.string.failed_accent))
Toast.makeText(
requireActivity(),
getString(R.string.failed_accent),
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
dismiss()
}
accentReset.setOnClickListener {
prefs.managerAccent = defAccentColor
mutableAccentColor.value = defAccentColor
dismiss()
}
}
}
}

View File

@ -1,66 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.vanced.manager.BuildConfig.MANAGER_LANGUAGES
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerLanguageBinding
import com.vanced.manager.ui.core.ThemedMaterialRadioButton
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.getLanguageFormat
import com.vanced.manager.utils.managerLang
class ManagerLanguageDialog : BindingBottomSheetDialogFragment<DialogManagerLanguageBinding>() {
companion object {
fun newInstance(): ManagerLanguageDialog = ManagerLanguageDialog().apply {
arguments = Bundle()
}
}
private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) }
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogManagerLanguageBinding.inflate(inflater, container, false)
override fun otherSetups() {
bindData()
}
private fun bindData() {
with(binding) {
addRadioButtons().forEach { mrb ->
languageRadiogroup.addView(mrb, MATCH_PARENT, WRAP_CONTENT)
}
val language = prefs.managerLang
root.findViewWithTag<ThemedMaterialRadioButton>(language)?.isChecked = true
languageSave.setOnClickListener {
val newPref = binding.languageRadiogroup.checkedButtonTag
if (language != newPref) {
prefs.managerLang = newPref
dismiss()
requireActivity().recreate()
} else {
dismiss()
}
}
}
}
private fun addRadioButtons() =
(arrayOf("System Default") + MANAGER_LANGUAGES).map { lang ->
ThemedMaterialRadioButton(requireActivity()).apply {
text = getLanguageFormat(requireActivity(), lang)
textSize = 18f
tag = lang
}
}
}

View File

@ -1,50 +0,0 @@
package com.vanced.manager.ui.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.google.android.material.radiobutton.MaterialRadioButton
import com.vanced.manager.core.ui.base.BindingBottomSheetDialogFragment
import com.vanced.manager.databinding.DialogManagerThemeBinding
import com.vanced.manager.utils.checkedButtonTag
import com.vanced.manager.utils.managerTheme
class ManagerThemeDialog : BindingBottomSheetDialogFragment<DialogManagerThemeBinding>() {
companion object {
fun newInstance(): ManagerThemeDialog = ManagerThemeDialog().apply {
arguments = Bundle()
}
}
private val prefs by lazy { getDefaultSharedPreferences(requireActivity()) }
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogManagerThemeBinding.inflate(inflater, container, false)
override fun otherSetups() {
bindData()
}
private fun bindData() {
with(binding) {
val theme = prefs.managerTheme
root.findViewWithTag<MaterialRadioButton>(theme).isChecked = true
themeSave.setOnClickListener {
val newPref = themeRadiogroup.checkedButtonTag
if (theme != newPref) {
prefs.managerTheme = newPref
dismiss()
requireActivity().recreate()
} else {
dismiss()
}
}
}
}
}

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