initial compose commit
This commit is contained in:
parent
e6f04a95c8
commit
092548e8bf
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
|
@ -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
|
||||
|
|
12
README.md
12
README.md
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
/release
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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()) }
|
||||
|
||||
}
|
|
@ -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()) }
|
||||
|
||||
}
|
|
@ -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() }
|
||||
}
|
|
@ -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()) }
|
||||
}
|
|
@ -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()) }
|
||||
|
||||
}
|
|
@ -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()) }
|
||||
|
||||
}
|
|
@ -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()) }
|
||||
}
|
|
@ -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) {
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package com.vanced.manager.domain.model
|
||||
|
||||
enum class AppStatus {
|
||||
|
||||
Install,
|
||||
Reinstall,
|
||||
Update,
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package com.vanced.manager.domain.model
|
||||
|
||||
data class Link(
|
||||
val title: String
|
||||
)
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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?
|
||||
)
|
|
@ -1,6 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
data class AppVersionsModel(
|
||||
val version: String,
|
||||
val value: String
|
||||
)
|
|
@ -1,5 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
enum class ButtonTag {
|
||||
INSTALL, UPDATE, REINSTALL
|
||||
}
|
|
@ -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)!!
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
data class LinkModel(
|
||||
val linkIcon: Drawable?,
|
||||
val linkUrl: String
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
data class NotifModel(
|
||||
val topic: String,
|
||||
val switchTitle: String,
|
||||
val switchSummary: String,
|
||||
val key: String
|
||||
)
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
data class SelectAppModel(
|
||||
val appName: String,
|
||||
val appDescription: String,
|
||||
val tag: String,
|
||||
var isChecked: Boolean
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -1,6 +0,0 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
data class VancedPrefModel(
|
||||
val name: String,
|
||||
val value: String
|
||||
)
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.vanced.manager.repository
|
||||
|
||||
import com.vanced.manager.domain.model.Json
|
||||
|
||||
interface JsonRepository {
|
||||
|
||||
suspend fun fetch(): Json
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
package com.vanced.manager.ui.composables
|
||||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
package com.vanced.manager.ui.compose
|
||||
|
|
@ -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)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
|
@ -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
|
||||
// )
|
||||
//}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!!)
|
||||
}
|
||||
}
|
|
@ -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!!)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue