implement proper installation
This commit is contained in:
parent
9d3529e8a5
commit
f7d0afc3b6
|
@ -79,12 +79,12 @@ val languages: String get() {
|
|||
|
||||
dependencies {
|
||||
implementation(kotlin("reflect"))
|
||||
implementation("androidx.core:core-ktx:1.6.0")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.appcompat:appcompat:1.3.1")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
implementation("androidx.browser:browser:1.3.0")
|
||||
|
||||
val composeVersion = "1.1.0-alpha06"
|
||||
val composeVersion = "1.1.0-beta01"
|
||||
implementation("androidx.compose.compiler:compiler:$composeVersion")
|
||||
implementation("androidx.compose.foundation:foundation:$composeVersion")
|
||||
implementation("androidx.compose.material:material-icons-core:$composeVersion")
|
||||
|
@ -96,9 +96,11 @@ dependencies {
|
|||
implementation("androidx.compose.ui:ui-util:$composeVersion")
|
||||
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||
|
||||
implementation("com.github.zsoltk:compose-router:0.28.0")
|
||||
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
|
||||
implementation("androidx.activity:activity-compose:1.3.1")
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
|
||||
val lifecycleVersion = "2.4.0-beta01"
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
|
||||
<service android:name="com.xinto.apkhelper.services.PackageManagerService" />
|
||||
|
||||
<service android:name=".core.installer.service.AppInstallService" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -18,7 +18,7 @@ abstract class AppDownloader {
|
|||
private lateinit var call: DownloadCall
|
||||
|
||||
abstract suspend fun download(
|
||||
appVersions: List<String>,
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
)
|
||||
|
||||
|
@ -41,9 +41,7 @@ abstract class AppDownloader {
|
|||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
if (body != null) {
|
||||
writeFile(body, downloadFile.fileName) { progress ->
|
||||
onProgress(progress)
|
||||
}
|
||||
writeFile(body, downloadFile.fileName, onProgress)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.vanced.manager.core.downloader.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.vanced.manager.core.downloader.api.MicrogAPI
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
|
@ -12,7 +13,7 @@ class MicrogDownloader(
|
|||
) : AppDownloader() {
|
||||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>,
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
downloadFiles(
|
||||
|
|
|
@ -17,18 +17,18 @@ class MusicDownloader(
|
|||
private val version by musicVersionPref
|
||||
private val variant by managerVariantPref
|
||||
|
||||
private lateinit var correctVersion: String
|
||||
private lateinit var absoluteVersion: String
|
||||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>,
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
correctVersion = getLatestOrProvidedAppVersion(version, appVersions)
|
||||
absoluteVersion = getLatestOrProvidedAppVersion(version, appVersions)
|
||||
|
||||
downloadFiles(
|
||||
downloadFiles = arrayOf(DownloadFile(
|
||||
call = musicAPI.getFiles(
|
||||
version = correctVersion,
|
||||
version = absoluteVersion,
|
||||
variant = variant,
|
||||
),
|
||||
fileName = "music.apk"
|
||||
|
@ -53,7 +53,7 @@ class MusicDownloader(
|
|||
|
||||
override fun getSavedFilePath(): String {
|
||||
val directory =
|
||||
File(context.getExternalFilesDir("vancedmusic")!!.path + "$correctVersion/$variant")
|
||||
File(context.getExternalFilesDir("vancedmusic")!!.path + "$absoluteVersion/$variant")
|
||||
|
||||
if (!directory.exists())
|
||||
directory.mkdirs()
|
||||
|
|
|
@ -22,13 +22,13 @@ class VancedDownloader(
|
|||
private val variant by managerVariantPref
|
||||
private val languages by vancedLanguagesPref
|
||||
|
||||
private lateinit var correctVersion: String
|
||||
private lateinit var absoluteVersion: String
|
||||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>,
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
correctVersion = getLatestOrProvidedAppVersion(version, appVersions)
|
||||
absoluteVersion = getLatestOrProvidedAppVersion(version, appVersions)
|
||||
|
||||
val files = arrayOf(
|
||||
getFile(
|
||||
|
@ -68,7 +68,7 @@ class VancedDownloader(
|
|||
|
||||
override fun getSavedFilePath(): String {
|
||||
val directory =
|
||||
File(context.getExternalFilesDir("vanced")!!.path + "$correctVersion/$variant")
|
||||
File(context.getExternalFilesDir("vanced")!!.path + "/$absoluteVersion/$variant")
|
||||
|
||||
if (!directory.exists())
|
||||
directory.mkdirs()
|
||||
|
@ -81,7 +81,7 @@ class VancedDownloader(
|
|||
apkName: String,
|
||||
) = DownloadFile(
|
||||
call = vancedAPI.getFiles(
|
||||
version = correctVersion,
|
||||
version = absoluteVersion,
|
||||
variant = variant,
|
||||
type = type,
|
||||
apkName = apkName
|
||||
|
|
|
@ -1,36 +1,7 @@
|
|||
package com.vanced.manager.core.installer.base
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.CallSuper
|
||||
import com.vanced.manager.core.util.log
|
||||
import com.xinto.apkhelper.statusCallback
|
||||
import com.xinto.apkhelper.statusCallbackBuilder
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
abstract class AppInstaller {
|
||||
|
||||
open class AppInstaller : KoinComponent {
|
||||
|
||||
val context: Context by inject()
|
||||
|
||||
@CallSuper
|
||||
open fun install(
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
setStatusCallback(onDone = onDone)
|
||||
}
|
||||
|
||||
private fun setStatusCallback(
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
statusCallback = statusCallbackBuilder(
|
||||
onInstall = { _, _ ->
|
||||
onDone()
|
||||
},
|
||||
onInstallFailed = { error, _, _ ->
|
||||
onDone()
|
||||
log("install", error)
|
||||
}
|
||||
)
|
||||
}
|
||||
abstract fun install(appVersions: List<String>?)
|
||||
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
package com.vanced.manager.core.installer.impl
|
||||
|
||||
import android.content.Context
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.xinto.apkhelper.installApk
|
||||
import com.vanced.manager.core.installer.util.installApp
|
||||
import java.io.File
|
||||
|
||||
class MicrogInstaller : AppInstaller() {
|
||||
class MicrogInstaller(
|
||||
private val context: Context
|
||||
) : AppInstaller() {
|
||||
|
||||
override fun install(
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
super.install(onDone)
|
||||
|
||||
installApk(
|
||||
apkPath = context.getExternalFilesDir("microg/microg.apk")!!.path,
|
||||
context = context
|
||||
)
|
||||
override fun install(appVersions: List<String>?) {
|
||||
val musicApk = File(context.getExternalFilesDir("microg/microg.apk")!!.path)
|
||||
installApp(musicApk, context)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,23 @@
|
|||
package com.vanced.manager.core.installer.impl
|
||||
|
||||
import android.content.Context
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.installApp
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.musicVersionPref
|
||||
import com.xinto.apkhelper.installApk
|
||||
import java.io.File
|
||||
|
||||
class MusicInstaller : AppInstaller() {
|
||||
|
||||
override fun install(
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
super.install(onDone)
|
||||
class MusicInstaller(
|
||||
private val context: Context
|
||||
) : AppInstaller() {
|
||||
|
||||
override fun install(appVersions: List<String>?) {
|
||||
val version by musicVersionPref
|
||||
val variant by managerVariantPref
|
||||
|
||||
installApk(
|
||||
apkPath = context.getExternalFilesDir("music/$version/$variant/music.apk")!!.path,
|
||||
context = context
|
||||
)
|
||||
val musicApk = File(context.getExternalFilesDir("music/$version/$variant/music.apk")!!.path)
|
||||
|
||||
installApp(musicApk, context)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,29 @@
|
|||
package com.vanced.manager.core.installer.impl
|
||||
|
||||
import android.content.Context
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.installSplitApp
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.vancedVersionPref
|
||||
import com.xinto.apkhelper.installSplitApks
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
|
||||
class VancedInstaller : AppInstaller() {
|
||||
|
||||
override fun install(
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
super.install(onDone)
|
||||
class VancedInstaller(
|
||||
private val context: Context
|
||||
) : AppInstaller() {
|
||||
|
||||
override fun install(appVersions: List<String>?) {
|
||||
val version by vancedVersionPref
|
||||
val variant by managerVariantPref
|
||||
|
||||
installSplitApks(
|
||||
apksPath = context.getExternalFilesDir("vanced/$version/$variant")!!.path,
|
||||
context = context
|
||||
)
|
||||
val absoluteVersion = getLatestOrProvidedAppVersion(version, appVersions)
|
||||
|
||||
val apks = context
|
||||
.getExternalFilesDir("vanced/$absoluteVersion/$variant")!!
|
||||
.listFiles { file ->
|
||||
file.extension == "apk"
|
||||
}
|
||||
|
||||
installSplitApp(apks!!, context)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.vanced.manager.core.installer.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.IBinder
|
||||
|
||||
class AppInstallService : Service() {
|
||||
|
||||
override fun onStartCommand(
|
||||
intent: Intent,
|
||||
flags: Int,
|
||||
startId: Int
|
||||
): Int {
|
||||
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
startActivity(
|
||||
intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT).apply {
|
||||
this?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
sendBroadcast(Intent().apply {
|
||||
action = APP_INSTALL_STATUS
|
||||
putExtra(EXTRA_INSTALL_STATUS, status)
|
||||
putExtra(EXTRA_INSTALL_EXTRA, intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE))
|
||||
})
|
||||
}
|
||||
}
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
companion object {
|
||||
const val APP_INSTALL_STATUS = "APP_INSTALL_STATUS"
|
||||
|
||||
const val EXTRA_INSTALL_STATUS = "EXTRA_INSTALL_STATUS"
|
||||
const val EXTRA_INSTALL_EXTRA = "EXTRA_INSTALL_EXTRA"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import com.vanced.manager.core.installer.service.AppInstallService
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable
|
||||
|
||||
fun installApp(apk: File, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
writeApkToSession(apk, session)
|
||||
session.commit(getIntentSender(context))
|
||||
session.close()
|
||||
}
|
||||
|
||||
fun installSplitApp(apks: Array<File>, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
for (apk in apks) {
|
||||
writeApkToSession(apk, session)
|
||||
}
|
||||
session.commit(getIntentSender(context))
|
||||
session.close()
|
||||
}
|
||||
|
||||
private val intentFlags
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
else
|
||||
0
|
||||
|
||||
private val sessionParams
|
||||
get() = PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||
).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIntentSender(context: Context) =
|
||||
PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
Intent(context, AppInstallService::class.java),
|
||||
intentFlags
|
||||
).intentSender
|
||||
|
||||
private fun writeApkToSession(
|
||||
apk: File,
|
||||
session: PackageInstaller.Session
|
||||
) {
|
||||
val inputStream = FileInputStream(apk)
|
||||
val outputStream = session.openWrite(apk.name, 0, apk.length())
|
||||
val buffer = ByteArray(byteArraySize)
|
||||
var length: Int
|
||||
while (inputStream.read(buffer).also { length = it } > 0) {
|
||||
outputStream.write(buffer, 0, length)
|
||||
}
|
||||
session.fsync(outputStream)
|
||||
inputStream.close()
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
}
|
|
@ -2,8 +2,11 @@ package com.vanced.manager.core.util
|
|||
|
||||
fun getLatestOrProvidedAppVersion(
|
||||
version: String,
|
||||
appVersions: List<String>
|
||||
appVersions: List<String>?
|
||||
): String {
|
||||
if (appVersions == null)
|
||||
return version
|
||||
|
||||
if (appVersions.contains(version))
|
||||
return version
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.vanced.manager.di
|
||||
|
||||
import android.content.Context
|
||||
import com.vanced.manager.core.installer.impl.MicrogInstaller
|
||||
import com.vanced.manager.core.installer.impl.MusicInstaller
|
||||
import com.vanced.manager.core.installer.impl.VancedInstaller
|
||||
|
@ -7,13 +8,19 @@ import org.koin.dsl.module
|
|||
|
||||
val installerModule = module {
|
||||
|
||||
fun provideVancedInstaller() = VancedInstaller()
|
||||
fun provideVancedInstaller(
|
||||
context: Context
|
||||
) = VancedInstaller(context)
|
||||
|
||||
fun provideMusicInstaller() = MusicInstaller()
|
||||
fun provideMusicInstaller(
|
||||
context: Context
|
||||
) = MusicInstaller(context)
|
||||
|
||||
fun provideMicrogInstaller() = MicrogInstaller()
|
||||
fun provideMicrogInstaller(
|
||||
context: Context
|
||||
) = MicrogInstaller(context)
|
||||
|
||||
single { provideVancedInstaller() }
|
||||
single { provideMusicInstaller() }
|
||||
single { provideMicrogInstaller() }
|
||||
single { provideVancedInstaller(get()) }
|
||||
single { provideMusicInstaller(get()) }
|
||||
single { provideMicrogInstaller(get()) }
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package com.vanced.manager.di
|
||||
|
||||
import com.vanced.manager.ui.viewmodel.InstallViewModel
|
||||
import com.vanced.manager.ui.viewmodel.MainViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val viewModelModule = module {
|
||||
viewModel { MainViewModel(get()) }
|
||||
viewModel { InstallViewModel(get(), get(), get(), get(), get(), get()) }
|
||||
}
|
|
@ -29,6 +29,6 @@ sealed class InstallationOption(
|
|||
|
||||
@Parcelize
|
||||
data class InstallationOptionItem(
|
||||
val displayText: String,
|
||||
val key: String,
|
||||
val displayText: (key: String) -> String,
|
||||
) : Parcelable
|
|
@ -23,7 +23,7 @@ class AppDtoMapper(
|
|||
|
||||
private val latestVersionRadioButton =
|
||||
InstallationOptionItem(
|
||||
displayText = context.getString(R.string.app_version_dialog_option_latest),
|
||||
displayText = { context.getString(R.string.app_version_dialog_option_latest) },
|
||||
key = "latest"
|
||||
)
|
||||
|
||||
|
@ -83,8 +83,10 @@ class AppDtoMapper(
|
|||
},
|
||||
items = appThemes?.map { theme ->
|
||||
InstallationOptionItem(
|
||||
displayText = theme.replaceFirstChar {
|
||||
it.titlecase(Locale.getDefault())
|
||||
displayText = {
|
||||
theme.replaceFirstChar {
|
||||
it.titlecase(Locale.getDefault())
|
||||
}
|
||||
},
|
||||
key = theme
|
||||
)
|
||||
|
@ -98,7 +100,7 @@ class AppDtoMapper(
|
|||
},
|
||||
items = appVersions?.map { version ->
|
||||
InstallationOptionItem(
|
||||
displayText = version,
|
||||
displayText = { "v$version" },
|
||||
key = version
|
||||
)
|
||||
}?.plus(latestVersionRadioButton)?.reversed() ?: emptyList(),
|
||||
|
@ -112,12 +114,15 @@ class AppDtoMapper(
|
|||
removeOption = {
|
||||
vancedLanguagesPref.save(vancedLanguagesPref.value.value - it)
|
||||
},
|
||||
items = appLanguages?.map { version ->
|
||||
items = appLanguages?.map { language ->
|
||||
InstallationOptionItem(
|
||||
displayText = version,
|
||||
key = version
|
||||
displayText = {
|
||||
val locale = Locale(it)
|
||||
locale.getDisplayName(locale)
|
||||
},
|
||||
key = language
|
||||
)
|
||||
}?.plus(latestVersionRadioButton)?.reversed() ?: emptyList(),
|
||||
} ?: emptyList(),
|
||||
),
|
||||
)
|
||||
MUSIC_NAME -> listOf(
|
||||
|
@ -129,7 +134,7 @@ class AppDtoMapper(
|
|||
},
|
||||
items = appVersions?.map { version ->
|
||||
InstallationOptionItem(
|
||||
displayText = version,
|
||||
displayText = { version },
|
||||
key = version
|
||||
)
|
||||
} ?: emptyList(),
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package com.vanced.manager.ui
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.material.*
|
||||
|
@ -14,12 +17,11 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.github.zsoltk.compose.backpress.BackPressHandler
|
||||
import com.github.zsoltk.compose.backpress.LocalBackPressHandler
|
||||
import com.github.zsoltk.compose.router.Router
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.vanced.manager.core.installer.service.AppInstallService
|
||||
import com.vanced.manager.ui.component.color.managerAnimatedColor
|
||||
import com.vanced.manager.ui.component.color.managerSurfaceColor
|
||||
import com.vanced.manager.ui.component.color.managerTextColor
|
||||
|
@ -30,160 +32,173 @@ import com.vanced.manager.ui.screens.*
|
|||
import com.vanced.manager.ui.theme.ManagerTheme
|
||||
import com.vanced.manager.ui.theme.isDark
|
||||
import com.vanced.manager.ui.util.Screen
|
||||
import com.vanced.manager.ui.viewmodel.InstallViewModel
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
ManagerTheme {
|
||||
MainActivityLayout()
|
||||
}
|
||||
private val installViewModel: InstallViewModel by inject()
|
||||
|
||||
private val backPressHandler = BackPressHandler()
|
||||
|
||||
private val installBroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action != AppInstallService.APP_INSTALL_STATUS) return
|
||||
|
||||
installViewModel.postInstallStatus(
|
||||
pmStatus = intent.getIntExtra(AppInstallService.EXTRA_INSTALL_STATUS, -999),
|
||||
extra = intent.getStringExtra(AppInstallService.EXTRA_INSTALL_EXTRA)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun MainActivityLayout() {
|
||||
val isMenuExpanded = remember { mutableStateOf(false) }
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
ManagerTheme {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
var currentScreen by remember { mutableStateOf<Screen>(Screen.Home) }
|
||||
|
||||
val surfaceColor = managerSurfaceColor()
|
||||
val surfaceColor = managerSurfaceColor()
|
||||
|
||||
val isDark = isDark()
|
||||
val isDark = isDark()
|
||||
|
||||
val navController = rememberAnimatedNavController()
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = surfaceColor,
|
||||
darkIcons = !isDark
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainToolbar(
|
||||
navController = navController,
|
||||
isMenuExpanded = isMenuExpanded
|
||||
)
|
||||
},
|
||||
backgroundColor = surfaceColor
|
||||
) {
|
||||
AnimatedNavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Home.route,
|
||||
enterTransition = { _, _ ->
|
||||
slideIntoContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.Start
|
||||
)
|
||||
},
|
||||
exitTransition = { _, _ ->
|
||||
slideOutOfContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.End
|
||||
)
|
||||
},
|
||||
popEnterTransition = { _, _ ->
|
||||
slideIntoContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.End
|
||||
)
|
||||
},
|
||||
popExitTransition = { _, _ ->
|
||||
slideOutOfContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.Start
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = surfaceColor,
|
||||
darkIcons = !isDark
|
||||
)
|
||||
}
|
||||
) {
|
||||
composable(Screen.Home.route) {
|
||||
HomeLayout(navController)
|
||||
}
|
||||
composable(Screen.Settings.route) {
|
||||
SettingsLayout()
|
||||
}
|
||||
composable(Screen.About.route) {
|
||||
AboutLayout()
|
||||
}
|
||||
composable(Screen.InstallPreferences.route) {
|
||||
val arguments = navController.previousBackStackEntry?.arguments
|
||||
|
||||
InstallPreferencesScreen(
|
||||
installationOptions = arguments?.getParcelableArrayList("app")!!
|
||||
)
|
||||
}
|
||||
composable(Screen.Install.route,) {
|
||||
val arguments = navController.previousBackStackEntry?.arguments
|
||||
CompositionLocalProvider(
|
||||
LocalBackPressHandler provides backPressHandler
|
||||
) {
|
||||
Router<Screen>("VancedManager", Screen.Home) { backStack ->
|
||||
val screen = backStack.last()
|
||||
currentScreen = screen
|
||||
|
||||
InstallScreen(
|
||||
appName = arguments?.getString("appName")!!,
|
||||
appVersions = arguments.getParcelableArrayList("appVersions")!!
|
||||
)
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
ToolbarTitleText(
|
||||
text = managerString(
|
||||
stringId = currentScreen.displayName
|
||||
)
|
||||
)
|
||||
},
|
||||
backgroundColor = managerAnimatedColor(color = MaterialTheme.colors.surface),
|
||||
actions = {
|
||||
if (currentScreen is Screen.Home) {
|
||||
IconButton(
|
||||
onClick = { isMenuExpanded = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null,
|
||||
tint = managerTextColor()
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = isMenuExpanded,
|
||||
onDismissRequest = {
|
||||
isMenuExpanded = false
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colors.surface),
|
||||
) {
|
||||
ManagerDropdownMenuItem(
|
||||
title = stringResource(id = Screen.Settings.displayName)
|
||||
) {
|
||||
isMenuExpanded = false
|
||||
backStack.push(Screen.Settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationIcon = if (currentScreen !is Screen.Home) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
backStack.pop()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIos,
|
||||
contentDescription = null,
|
||||
tint = managerTextColor()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
elevation = 0.dp
|
||||
)
|
||||
},
|
||||
backgroundColor = surfaceColor
|
||||
) {
|
||||
when (screen) {
|
||||
is Screen.Home -> {
|
||||
HomeLayout(
|
||||
onAppInstallPress = { appName, appVersions, installationOptions ->
|
||||
if (installationOptions != null) {
|
||||
backStack.push(Screen.InstallPreferences(appName, appVersions, installationOptions))
|
||||
} else {
|
||||
backStack.push(Screen.Install(appName, appVersions))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
is Screen.Settings -> {
|
||||
SettingsLayout()
|
||||
}
|
||||
is Screen.About -> {
|
||||
AboutLayout()
|
||||
}
|
||||
is Screen.Logs -> {
|
||||
|
||||
}
|
||||
is Screen.InstallPreferences -> {
|
||||
InstallPreferencesScreen(
|
||||
installationOptions = screen.appInstallationOptions,
|
||||
onDoneClick = {
|
||||
backStack.push(Screen.Install(screen.appName, screen.appVersions))
|
||||
}
|
||||
)
|
||||
}
|
||||
is Screen.Install -> {
|
||||
InstallScreen(screen.appName, screen.appVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainToolbar(
|
||||
navController: NavHostController,
|
||||
isMenuExpanded: MutableState<Boolean>
|
||||
) {
|
||||
val currentScreenRoute =
|
||||
navController.currentBackStackEntryAsState().value?.destination?.route
|
||||
override fun onBackPressed() {
|
||||
if (!backPressHandler.handle())
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
TopAppBar(
|
||||
title = {
|
||||
ToolbarTitleText(
|
||||
text = managerString(
|
||||
stringId = Screen.values().find { it.route == currentScreenRoute }?.displayName
|
||||
)
|
||||
)
|
||||
},
|
||||
backgroundColor = managerAnimatedColor(color = MaterialTheme.colors.surface),
|
||||
actions = {
|
||||
if (currentScreenRoute == Screen.Home.route) {
|
||||
IconButton(
|
||||
onClick = { isMenuExpanded.value = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null,
|
||||
tint = managerTextColor()
|
||||
)
|
||||
}
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
DropdownMenu(
|
||||
expanded = isMenuExpanded.value,
|
||||
onDismissRequest = {
|
||||
isMenuExpanded.value = false
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colors.surface),
|
||||
) {
|
||||
for (screen in Screen.values()) {
|
||||
ManagerDropdownMenuItem(
|
||||
title = stringResource(id = screen.displayName)
|
||||
) {
|
||||
isMenuExpanded.value = false
|
||||
navController.navigate(screen.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationIcon = if (currentScreenRoute != Screen.Home.route) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
navController.popBackStack()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIos,
|
||||
contentDescription = null,
|
||||
tint = managerTextColor()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
elevation = 0.dp
|
||||
registerReceiver(
|
||||
installBroadcastReceiver,
|
||||
IntentFilter().apply {
|
||||
addAction(AppInstallService.APP_INSTALL_STATUS)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
unregisterReceiver(installBroadcastReceiver)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -8,15 +8,18 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal
|
||||
|
||||
@Composable
|
||||
fun ManagerLazyColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
itemSpacing: Dp = 0.dp,
|
||||
contentPadding: PaddingValues = PaddingValues(12.dp),
|
||||
content: LazyListScope.() -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(12.dp),
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(itemSpacing),
|
||||
content = content
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|||
import com.vanced.manager.R
|
||||
import com.vanced.manager.core.util.socialMedia
|
||||
import com.vanced.manager.core.util.sponsors
|
||||
import com.vanced.manager.domain.model.InstallationOption
|
||||
import com.vanced.manager.ui.component.card.ManagerLinkCard
|
||||
import com.vanced.manager.ui.component.dialog.ManagerDialog
|
||||
import com.vanced.manager.ui.component.layout.ManagerScrollableColumn
|
||||
|
@ -35,7 +36,11 @@ import org.koin.androidx.compose.getViewModel
|
|||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun HomeLayout(
|
||||
navController: NavController
|
||||
onAppInstallPress: (
|
||||
appName: String,
|
||||
appVersions: List<String>?,
|
||||
installationOptions: List<InstallationOption>?
|
||||
) -> Unit
|
||||
) {
|
||||
val viewModel: MainViewModel = getViewModel()
|
||||
val appState by viewModel.appState.collectAsState()
|
||||
|
@ -78,12 +83,7 @@ fun HomeLayout(
|
|||
appInstalledVersion = app.installedVersion,
|
||||
appRemoteVersion = app.remoteVersion,
|
||||
onDownloadClick = {
|
||||
if (app.installationOptions != null) {
|
||||
navController.navigate(Screen.InstallPreferences.route)
|
||||
} else {
|
||||
navController.navigate(Screen.Install.route)
|
||||
}
|
||||
|
||||
onAppInstallPress(app.name, app.versions, app.installationOptions)
|
||||
},
|
||||
onUninstallClick = { /*TODO*/ },
|
||||
onLaunchClick = { /*TODO*/ },
|
||||
|
|
|
@ -5,67 +5,93 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Done
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
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.util.fastForEach
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.vanced.manager.domain.model.InstallationOption
|
||||
import com.vanced.manager.ui.component.card.ManagerCard
|
||||
import com.vanced.manager.ui.component.layout.ManagerLazyColumn
|
||||
import com.vanced.manager.ui.component.card.ManagerClickableThemedCard
|
||||
import com.vanced.manager.ui.component.layout.ManagerScrollableColumn
|
||||
import com.vanced.manager.ui.component.text.ManagerText
|
||||
import com.vanced.manager.ui.resources.managerString
|
||||
import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal
|
||||
import com.vanced.manager.ui.util.DefaultContentPaddingVertical
|
||||
import com.vanced.manager.ui.widget.list.CheckboxItem
|
||||
import com.vanced.manager.ui.widget.list.RadiobuttonItem
|
||||
|
||||
@Composable
|
||||
fun InstallPreferencesScreen(
|
||||
installationOptions: List<InstallationOption>
|
||||
installationOptions: List<InstallationOption>,
|
||||
onDoneClick: () -> Unit
|
||||
) {
|
||||
var selectedOptionIndex by rememberSaveable { mutableStateOf(0) }
|
||||
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = onDoneClick
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Done,
|
||||
contentDescription = "Done"
|
||||
)
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
ManagerScrollableColumn(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
itemSpacing = DefaultContentPaddingVertical
|
||||
) {
|
||||
installationOptions.fastForEachIndexed { index, installationOption ->
|
||||
ManagerCard(onClick = {
|
||||
selectedOptionIndex = index
|
||||
}) {
|
||||
Column {
|
||||
ManagerClickableThemedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DefaultContentPaddingHorizontal),
|
||||
onClick = {
|
||||
selectedOptionIndex = index
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
ManagerText(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = managerString(installationOption.itemTitleId),
|
||||
textStyle = TextStyle(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = index == selectedOptionIndex
|
||||
) {
|
||||
ManagerLazyColumn(
|
||||
ManagerScrollableColumn(
|
||||
modifier = Modifier.sizeIn(
|
||||
minHeight = 400.dp,
|
||||
maxHeight = 400.dp
|
||||
)
|
||||
) {
|
||||
when (installationOption) {
|
||||
is InstallationOption.MultiSelect -> {
|
||||
items(installationOption.items) { item ->
|
||||
installationOption.items.fastForEach { item ->
|
||||
val preference = installationOption.getOption()
|
||||
CheckboxItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = item.displayText,
|
||||
text = item.displayText(item.key),
|
||||
isChecked = preference.contains(item.key),
|
||||
onCheck = {
|
||||
if (it) {
|
||||
|
@ -78,11 +104,11 @@ fun InstallPreferencesScreen(
|
|||
}
|
||||
}
|
||||
is InstallationOption.SingleSelect -> {
|
||||
items(installationOption.items) { item ->
|
||||
installationOption.items.fastForEach { item ->
|
||||
val preference = installationOption.getOption()
|
||||
RadiobuttonItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = item.displayText,
|
||||
text = item.displayText(item.key),
|
||||
isSelected = preference == item.key,
|
||||
onSelect = {
|
||||
installationOption.setOption(item.key)
|
||||
|
|
|
@ -2,19 +2,16 @@ package com.vanced.manager.ui.screens
|
|||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowDropDown
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
@ -25,161 +22,123 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vanced.manager.core.downloader.impl.MicrogDownloader
|
||||
import com.vanced.manager.core.downloader.impl.MusicDownloader
|
||||
import com.vanced.manager.core.downloader.impl.VancedDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.installer.impl.MicrogInstaller
|
||||
import com.vanced.manager.core.installer.impl.MusicInstaller
|
||||
import com.vanced.manager.core.installer.impl.VancedInstaller
|
||||
import com.vanced.manager.network.util.MICROG_NAME
|
||||
import com.vanced.manager.network.util.MUSIC_NAME
|
||||
import com.vanced.manager.network.util.VANCED_NAME
|
||||
import com.vanced.manager.ui.component.layout.ManagerLazyColumn
|
||||
import com.vanced.manager.ui.component.modifier.managerClickable
|
||||
import com.vanced.manager.ui.component.progressindicator.ManagerProgressIndicator
|
||||
import com.vanced.manager.ui.component.text.ManagerText
|
||||
import com.vanced.manager.ui.viewmodel.MainViewModel
|
||||
import org.koin.androidx.compose.get
|
||||
import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal
|
||||
import com.vanced.manager.ui.util.DefaultContentPaddingVertical
|
||||
import com.vanced.manager.ui.viewmodel.InstallViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
sealed class Log {
|
||||
data class Info(val infoText: String) : Log()
|
||||
data class Success(val successText: String) : Log()
|
||||
data class Error(
|
||||
val displayText: String,
|
||||
val stacktrace: String,
|
||||
) : Log()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun InstallScreen(
|
||||
appName: String,
|
||||
appVersions: List<String>
|
||||
appVersions: List<String>?
|
||||
) {
|
||||
val logs = rememberSaveable { mutableStateListOf<Log>() }
|
||||
val viewModel: InstallViewModel = getViewModel()
|
||||
|
||||
var progress by rememberSaveable { mutableStateOf(0f) }
|
||||
var installing by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val viewModel: MainViewModel = getViewModel()
|
||||
|
||||
val downloader = when (appName) {
|
||||
VANCED_NAME -> get<VancedDownloader>()
|
||||
MUSIC_NAME -> get<MusicDownloader>()
|
||||
MICROG_NAME -> get<MicrogDownloader>()
|
||||
else -> throw IllegalArgumentException("$appName is not a valid app")
|
||||
}
|
||||
|
||||
val installer = when (appName) {
|
||||
VANCED_NAME -> get<VancedInstaller>()
|
||||
MUSIC_NAME -> get<MusicInstaller>()
|
||||
MICROG_NAME -> get<MicrogInstaller>()
|
||||
else -> throw IllegalArgumentException("$appName is not a valid app")
|
||||
}
|
||||
|
||||
//FIXME this is absolutely bad, must move to WorkManager
|
||||
LaunchedEffect(true) {
|
||||
downloader.download(appVersions) { status ->
|
||||
when (status) {
|
||||
is DownloadStatus.File -> logs.add(Log.Info("Downloading ${status.fileName}"))
|
||||
is DownloadStatus.Error -> logs.add(Log.Error(
|
||||
displayText = status.displayError,
|
||||
stacktrace = status.stacktrace
|
||||
))
|
||||
is DownloadStatus.Progress -> progress = status.progress
|
||||
is DownloadStatus.StartInstall -> {
|
||||
installing = true
|
||||
installer.install {
|
||||
viewModel.fetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModel.startAppProcess(appName, appVersions)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
if (installing) {
|
||||
ManagerProgressIndicator()
|
||||
} else {
|
||||
ManagerProgressIndicator(progress)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = { /*TODO*/ }) {
|
||||
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
ManagerLazyColumn(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
) {
|
||||
items(logs) { log ->
|
||||
stickyHeader {
|
||||
when (val status = viewModel.status) {
|
||||
is InstallViewModel.Status.Progress -> {
|
||||
ManagerProgressIndicator(status.progress)
|
||||
}
|
||||
is InstallViewModel.Status.Installing -> {
|
||||
ManagerProgressIndicator()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(DefaultContentPaddingVertical))
|
||||
}
|
||||
|
||||
items(viewModel.logs) { log ->
|
||||
when (log) {
|
||||
is Log.Success -> {
|
||||
is InstallViewModel.Log.Success -> {
|
||||
ManagerText(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DefaultContentPaddingHorizontal),
|
||||
text = log.successText,
|
||||
textStyle = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Green
|
||||
color = Color.Blue
|
||||
),
|
||||
)
|
||||
}
|
||||
is Log.Info -> {
|
||||
is InstallViewModel.Log.Info -> {
|
||||
ManagerText(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DefaultContentPaddingHorizontal),
|
||||
text = log.infoText,
|
||||
textStyle = TextStyle(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Green
|
||||
color = Color.Black
|
||||
),
|
||||
)
|
||||
}
|
||||
is Log.Error -> {
|
||||
is InstallViewModel.Log.Error -> {
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
val iconRotation by animateFloatAsState(if (visible) 0f else 90f)
|
||||
Row(
|
||||
modifier = Modifier.managerClickable {
|
||||
visible = !visible
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
val iconRotation by animateFloatAsState(if (visible) -90f else 0f)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.managerClickable {
|
||||
visible = !visible
|
||||
}
|
||||
.padding(horizontal = DefaultContentPaddingHorizontal),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.rotate(iconRotation),
|
||||
imageVector = Icons.Rounded.ArrowDropDown,
|
||||
contentDescription = "expand",
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ManagerText(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.error)) {
|
||||
append(log.displayText)
|
||||
append(": ")
|
||||
}
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.error.copy(alpha = 0.7f))) {
|
||||
append(log.stacktrace)
|
||||
}
|
||||
},
|
||||
textStyle = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Green
|
||||
),
|
||||
)
|
||||
AnimatedVisibility(visible) {
|
||||
ManagerText(
|
||||
text = log.stacktrace,
|
||||
textStyle = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Green
|
||||
),
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.rotate(iconRotation),
|
||||
imageVector = Icons.Rounded.ArrowDropDown,
|
||||
contentDescription = "expand",
|
||||
tint = MaterialTheme.colors.error
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible) {
|
||||
ManagerText(
|
||||
text = log.stacktrace,
|
||||
textStyle = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colors.error.copy(alpha = 0.7f)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,37 +3,48 @@ package com.vanced.manager.ui.util
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vanced.manager.R
|
||||
import com.vanced.manager.domain.model.InstallationOption
|
||||
import com.vanced.manager.ui.screens.AboutLayout
|
||||
import com.vanced.manager.ui.screens.HomeLayout
|
||||
import com.vanced.manager.ui.screens.LogLayout
|
||||
import com.vanced.manager.ui.screens.SettingsLayout
|
||||
|
||||
enum class Screen(
|
||||
sealed class Screen(
|
||||
val route: String,
|
||||
@StringRes val displayName: Int,
|
||||
) {
|
||||
Home(
|
||||
object Home : Screen(
|
||||
route = "home",
|
||||
displayName = R.string.app_name
|
||||
),
|
||||
Settings(
|
||||
)
|
||||
|
||||
object Settings: Screen(
|
||||
route = "settings",
|
||||
displayName = R.string.toolbar_settings,
|
||||
),
|
||||
About(
|
||||
)
|
||||
|
||||
object About: Screen(
|
||||
route = "about",
|
||||
displayName = R.string.toolbar_about,
|
||||
),
|
||||
Logs(
|
||||
)
|
||||
object Logs : Screen(
|
||||
route = "logs",
|
||||
displayName = R.string.toolbar_logs,
|
||||
),
|
||||
InstallPreferences(
|
||||
)
|
||||
data class InstallPreferences(
|
||||
val appName: String,
|
||||
val appVersions: List<String>?,
|
||||
val appInstallationOptions: List<InstallationOption>
|
||||
) : Screen(
|
||||
route = "installpreferences",
|
||||
displayName = R.string.toolbar_installation_preferences
|
||||
),
|
||||
Install(
|
||||
)
|
||||
|
||||
data class Install(
|
||||
val appName: String,
|
||||
val appVersions: List<String>?
|
||||
) : Screen(
|
||||
route = "install",
|
||||
displayName = R.string.toolbar_install
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
package com.vanced.manager.ui.viewmodel
|
||||
|
||||
import android.content.pm.PackageInstaller
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vanced.manager.core.downloader.impl.MicrogDownloader
|
||||
import com.vanced.manager.core.downloader.impl.MusicDownloader
|
||||
import com.vanced.manager.core.downloader.impl.VancedDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.installer.impl.MicrogInstaller
|
||||
import com.vanced.manager.core.installer.impl.MusicInstaller
|
||||
import com.vanced.manager.core.installer.impl.VancedInstaller
|
||||
import com.vanced.manager.network.util.MICROG_NAME
|
||||
import com.vanced.manager.network.util.MUSIC_NAME
|
||||
import com.vanced.manager.network.util.VANCED_NAME
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class InstallViewModel(
|
||||
private val vancedDownloader: VancedDownloader,
|
||||
private val musicDownloader: MusicDownloader,
|
||||
private val microgDownloader: MicrogDownloader,
|
||||
|
||||
private val vancedInstaller: VancedInstaller,
|
||||
private val musicInstaller: MusicInstaller,
|
||||
private val microgInstaller: MicrogInstaller,
|
||||
) : ViewModel() {
|
||||
|
||||
sealed class Log {
|
||||
data class Info(val infoText: String) : Log()
|
||||
data class Success(val successText: String) : Log()
|
||||
data class Error(
|
||||
val displayText: String,
|
||||
val stacktrace: String,
|
||||
) : Log()
|
||||
}
|
||||
|
||||
sealed class Status {
|
||||
object Idle : Status()
|
||||
object Installing : Status()
|
||||
object Installed : Status()
|
||||
object Failure : Status()
|
||||
data class Progress(val progress: Float) : Status()
|
||||
}
|
||||
|
||||
val logs = mutableStateListOf<Log>()
|
||||
|
||||
var status by mutableStateOf<Status>(Status.Idle)
|
||||
private set
|
||||
|
||||
//TODO Move to WorkManager
|
||||
fun startAppProcess(
|
||||
appName: String,
|
||||
appVersions: List<String>?
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
downloadApp(appName, appVersions)
|
||||
}
|
||||
}
|
||||
|
||||
fun postInstallStatus(pmStatus: Int, extra: String) {
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
status = Status.Installed
|
||||
logs.add(Log.Success("Successfully installed"))
|
||||
} else {
|
||||
status = Status.Failure
|
||||
logs.add(Log.Error("Failed to install app", extra))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun downloadApp(
|
||||
appName: String,
|
||||
appVersions: List<String>?,
|
||||
) {
|
||||
val downloader = getDownloader(appName)
|
||||
|
||||
downloader.download(appVersions) { downloadStatus ->
|
||||
when (downloadStatus) {
|
||||
is DownloadStatus.File -> logs.add(Log.Info("Downloading ${downloadStatus.fileName}"))
|
||||
is DownloadStatus.Error -> logs.add(Log.Error(
|
||||
displayText = downloadStatus.displayError,
|
||||
stacktrace = downloadStatus.stacktrace
|
||||
))
|
||||
is DownloadStatus.Progress -> status = Status.Progress(downloadStatus.progress / 100)
|
||||
is DownloadStatus.StartInstall -> {
|
||||
logs.add(Log.Success("Successfully downloaded $appName"))
|
||||
installApp(appName, appVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installApp(
|
||||
appName: String,
|
||||
appVersions: List<String>?,
|
||||
) {
|
||||
val installer = getInstaller(appName)
|
||||
|
||||
status = Status.Installing
|
||||
|
||||
installer.install(appVersions)
|
||||
}
|
||||
|
||||
private fun getDownloader(
|
||||
appName: String
|
||||
) = when (appName) {
|
||||
VANCED_NAME -> vancedDownloader
|
||||
MUSIC_NAME -> musicDownloader
|
||||
MICROG_NAME -> microgDownloader
|
||||
else -> throw IllegalArgumentException("$appName is not a valid app")
|
||||
}
|
||||
|
||||
private fun getInstaller(
|
||||
appName: String
|
||||
) = when (appName) {
|
||||
VANCED_NAME -> vancedInstaller
|
||||
MUSIC_NAME -> musicInstaller
|
||||
MICROG_NAME -> microgInstaller
|
||||
else -> throw IllegalArgumentException("$appName is not a valid app")
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
logs.clear()
|
||||
status = Status.Idle
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,9 @@ package com.vanced.manager.ui.viewmodel
|
|||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.domain.model.App
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.musicEnabled
|
||||
|
|
Loading…
Reference in New Issue