Feat: Initial home ui + release channel option

This commit is contained in:
wingio 2023-03-17 14:50:49 -04:00
parent 92a650b067
commit d6fa04f024
28 changed files with 491 additions and 59 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="me.schlaubi.intellij_gradle_version_checker.settings.ProjectPersistentGradleVersionSettings">
<option name="ignoreOutdatedVersion" value="true" />
</component>
</project>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@ -3,6 +3,7 @@ import java.io.ByteArrayOutputStream
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.serialization") version "1.7.20"
}
android {
@ -13,8 +14,8 @@ android {
applicationId = "dev.beefers.vendetta.manager"
minSdk = 24
targetSdk = 33
versionCode = 1000
versionName = "1.0.0"
versionCode = 1010
versionName = "1.0.1"
buildConfigField("String", "GIT_BRANCH", "\"${getCurrentBranch()}\"")
buildConfigField("String", "GIT_COMMIT", "\"${getLatestCommit()}\"")

View File

@ -11,7 +11,7 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionCode": 1000,
"versionName": "1.0.0",
"outputFile": "app-release.apk"
}

View File

@ -1,7 +1,7 @@
package dev.beefers.vendetta.manager.di
import dev.beefers.vendetta.manager.network.service.GithubService
import dev.beefers.vendetta.manager.network.service.HttpService
import dev.beefers.vendetta.manager.network.service.RestService
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
@ -14,7 +14,7 @@ val httpModule = module {
fun provideJson() = Json {
ignoreUnknownKeys = true
coerceInputValues = true
isLenient = true
}
fun provideHttpClient(json: Json) = HttpClient(CIO) {
@ -26,6 +26,6 @@ val httpModule = module {
singleOf(::provideJson)
singleOf(::provideHttpClient)
singleOf(::HttpService)
singleOf(::GithubService)
singleOf(::RestService)
}

View File

@ -1,6 +1,7 @@
package dev.beefers.vendetta.manager.di
import dev.beefers.vendetta.manager.domain.manager.DownloadManager
import dev.beefers.vendetta.manager.domain.manager.InstallManager
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
@ -8,4 +9,5 @@ import org.koin.dsl.module
val managerModule = module {
singleOf(::DownloadManager)
singleOf(::PreferenceManager)
singleOf(::InstallManager)
}

View File

@ -1,9 +1,9 @@
package dev.beefers.vendetta.manager.di
import dev.beefers.vendetta.manager.domain.repository.GithubRepository
import dev.beefers.vendetta.manager.domain.repository.RestRepository
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val repositoryModule = module {
singleOf(::GithubRepository)
singleOf(::RestRepository)
}

View File

@ -1,5 +1,6 @@
package dev.beefers.vendetta.manager.di
import dev.beefers.vendetta.manager.ui.viewmodel.home.HomeViewModel
import dev.beefers.vendetta.manager.ui.viewmodel.installer.InstallerViewModel
import dev.beefers.vendetta.manager.ui.viewmodel.main.MainViewModel
import dev.beefers.vendetta.manager.ui.viewmodel.settings.SettingsViewModel
@ -10,4 +11,5 @@ val viewModelModule = module {
factoryOf(::InstallerViewModel)
factoryOf(::SettingsViewModel)
factoryOf(::MainViewModel)
factoryOf(::HomeViewModel)
}

View File

@ -0,0 +1,66 @@
package dev.beefers.vendetta.manager.domain.manager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.beefers.vendetta.manager.installer.service.InstallService
class InstallManager(
private val context: Context,
private val prefs: PreferenceManager,
) {
var current by mutableStateOf<PackageInfo?>(null)
init {
getInstalled()
}
fun getInstalled() {
current = try {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
context.packageManager.getPackageInfo(
prefs.packageName.ifBlank { "dev.beefers.vendetta" },
PackageManager.PackageInfoFlags.of(
0L
)
)
}
else -> {
context.packageManager.getPackageInfo(
prefs.packageName.ifBlank { "dev.beefers.vendetta" },
0
)
}
}
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
fun uninstall() {
current?.let {
val callbackIntent = Intent(context, InstallService::class.java).apply {
action = "vendetta.actions.ACTION_UNINSTALL"
}
val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getService(context, 0, callbackIntent, PendingIntent.FLAG_MUTABLE)
} else {
PendingIntent.getService(context, 0, callbackIntent, 0)
}
context.packageManager.packageInstaller.uninstall(
it.packageName,
contentIntent.intentSender
)
}
}
}

View File

@ -5,6 +5,7 @@ import android.os.Build
import androidx.annotation.StringRes
import dev.beefers.vendetta.manager.R
import dev.beefers.vendetta.manager.domain.manager.base.BasePreferenceManager
import dev.beefers.vendetta.manager.utils.DiscordVersion
class PreferenceManager(private val context: Context) :
BasePreferenceManager(context.getSharedPreferences("prefs", Context.MODE_PRIVATE)) {
@ -25,6 +26,8 @@ class PreferenceManager(private val context: Context) :
var theme by enumPreference("theme", Theme.SYSTEM)
var channel by enumPreference("channel", DiscordVersion.Type.STABLE)
}
enum class Theme(@StringRes val labelRes: Int) {

View File

@ -1,11 +0,0 @@
package dev.beefers.vendetta.manager.domain.repository
import dev.beefers.vendetta.manager.network.service.GithubService
class GithubRepository(
private val service: GithubService
) {
suspend fun getLatestRelease() = service.getLatestRelease()
}

View File

@ -0,0 +1,21 @@
package dev.beefers.vendetta.manager.domain.repository
import dev.beefers.vendetta.manager.network.service.RestService
import dev.beefers.vendetta.manager.network.utils.transform
import dev.beefers.vendetta.manager.utils.DiscordVersion
class RestRepository(
private val service: RestService
) {
suspend fun getLatestRelease() = service.getLatestRelease()
suspend fun getLatestDiscordVersions() = service.getLatestDiscordVersions().transform {
mapOf(
DiscordVersion.Type.ALPHA to DiscordVersion.fromVersionCode(it.latest.alpha),
DiscordVersion.Type.BETA to DiscordVersion.fromVersionCode(it.latest.beta),
DiscordVersion.Type.STABLE to DiscordVersion.fromVersionCode(it.latest.stable)
)
}
}

View File

@ -5,10 +5,17 @@ import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.IBinder
import dev.beefers.vendetta.manager.R
import dev.beefers.vendetta.manager.domain.manager.InstallManager
import dev.beefers.vendetta.manager.utils.showToast
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class InstallService : Service(), KoinComponent {
private val installManager: InstallManager by inject()
class InstallService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val isInstall = intent.action == "vendetta.actions.ACTION_INSTALL"
when (val statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
@Suppress("DEPRECATION") // No.
@ -18,11 +25,15 @@ class InstallService : Service() {
startActivity(confirmationIntent)
}
PackageInstaller.STATUS_SUCCESS -> showToast(R.string.installer_success)
PackageInstaller.STATUS_FAILURE_ABORTED -> showToast(R.string.installer_aborted)
PackageInstaller.STATUS_SUCCESS -> {
if(isInstall) showToast(R.string.installer_success)
installManager.getInstalled()
}
PackageInstaller.STATUS_FAILURE_ABORTED -> if(isInstall) showToast(R.string.installer_aborted)
else -> {
showToast(R.string.installer_failed, statusCode)
if(isInstall) showToast(R.string.installer_failed, statusCode)
}
}
@ -31,4 +42,5 @@ class InstallService : Service() {
}
override fun onBind(intent: Intent): IBinder? = null
}

View File

@ -32,7 +32,9 @@ fun Context.installApks(silent: Boolean = false, vararg apks: File) {
}
}
val callbackIntent = Intent(this, InstallService::class.java)
val callbackIntent = Intent(this, InstallService::class.java).apply {
action = "vendetta.actions.ACTION_INSTALL"
}
@SuppressLint("UnspecifiedImmutableFlag")
val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

View File

@ -0,0 +1,17 @@
package dev.beefers.vendetta.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class Index(
val latest: Versions
) {
@Serializable
data class Versions(
val alpha: String,
val beta: String,
val stable: String
)
}

View File

@ -1,11 +1,12 @@
package dev.beefers.vendetta.manager.network.service
import dev.beefers.vendetta.manager.network.dto.Index
import dev.beefers.vendetta.manager.network.dto.Release
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GithubService(
class RestService(
private val httpService: HttpService
) {
@ -15,4 +16,10 @@ class GithubService(
}
}
suspend fun getLatestDiscordVersions() = withContext(Dispatchers.IO) {
httpService.request<Index> {
url("https://discord.k6.tf/index.json")
}
}
}

View File

@ -23,4 +23,13 @@ val <D> ApiResponse<D>.dataOrThrow
inline fun <D> ApiResponse<D>.ifSuccessful(block: (D) -> Unit) {
if (this is ApiResponse.Success) block(data)
}
}
@Suppress("UNCHECKED_CAST")
fun <T, R> ApiResponse<T>.transform(block: (T) -> R): ApiResponse<R> {
return when (this) {
is ApiResponse.Success -> ApiResponse.Success(block(data))
is ApiResponse.Error -> this as ApiResponse.Error<R>
is ApiResponse.Failure -> this as ApiResponse.Failure<R>
}
}

View File

@ -0,0 +1,60 @@
package dev.beefers.vendetta.manager.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@Composable
fun RowScope.SegmentedButton(
icon: Any,
iconDescription: String? = null,
text: String,
onClick: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
modifier = Modifier
.clickable(onClick = onClick)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.weight(1f)
.padding(16.dp)
) {
when (icon) {
is ImageVector -> {
Icon(
imageVector = icon,
contentDescription = iconDescription,
tint = MaterialTheme.colorScheme.primary
)
}
is Painter -> {
Icon(
painter = icon,
contentDescription = iconDescription,
tint = MaterialTheme.colorScheme.primary
)
}
}
Text(
text = text,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary
)
}
}

View File

@ -1,29 +1,49 @@
package dev.beefers.vendetta.manager.ui.screen.home
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material3.Button
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.TabOptions
import dev.beefers.vendetta.manager.R
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import dev.beefers.vendetta.manager.ui.components.SegmentedButton
import dev.beefers.vendetta.manager.ui.screen.installer.InstallerScreen
import dev.beefers.vendetta.manager.ui.viewmodel.home.HomeViewModel
import dev.beefers.vendetta.manager.utils.DiscordVersion
import dev.beefers.vendetta.manager.utils.ManagerTab
import dev.beefers.vendetta.manager.utils.TabOptions
import dev.beefers.vendetta.manager.utils.navigate
import org.koin.androidx.compose.get
class HomeScreen : ManagerTab {
override val options: TabOptions
@ -36,24 +56,105 @@ class HomeScreen : ManagerTab {
@Composable
override fun Content() {
val nav = LocalNavigator.currentOrThrow
val prefs: PreferenceManager = get()
val viewModel: HomeViewModel = getScreenModel()
val iconColor = when {
prefs.patchIcon -> Color(0xFF3AB8BA)
prefs.channel == DiscordVersion.Type.ALPHA -> Color(0xFFFBB33C)
else -> Color(0xFF5865F2)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.padding(16.dp)
.fillMaxSize()
.fillMaxWidth()
) {
Button(
onClick = { nav.navigate(InstallerScreen()) },
Image(
painter = painterResource(id = R.drawable.ic_discord_icon),
contentDescription = null,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
.background(iconColor)
)
Text(
text = prefs.appName,
style = MaterialTheme.typography.titleLarge
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.action_install))
AnimatedVisibility(visible = viewModel.discordVersions != null) {
Text(
text = "Latest: ${viewModel.discordVersions!![prefs.channel]!!}",
style = MaterialTheme.typography.labelLarge,
color = LocalContentColor.current.copy(alpha = 0.5f),
textAlign = TextAlign.Center
)
}
AnimatedVisibility(visible = viewModel.installManager.current != null) {
Text(
text = "Current: ${viewModel.installManager.current!!.versionName}",
style = MaterialTheme.typography.labelLarge,
color = LocalContentColor.current.copy(alpha = 0.5f),
textAlign = TextAlign.Center
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "This UI is temporary, check back later for something prettier",
textAlign = TextAlign.Center,
Button(
onClick = {
val version = viewModel.discordVersions!![prefs.channel]!!
nav.navigate(InstallerScreen(version))
},
enabled = viewModel.discordVersions != null,
modifier = Modifier.fillMaxWidth()
)
) {
val label = when {
viewModel.installManager.current == null -> R.string.action_install
viewModel.installManager.current?.versionName == viewModel.discordVersions?.get(
prefs.channel
).toString() -> R.string.action_reinstall
else -> R.string.action_update
}
Text(stringResource(label))
}
AnimatedVisibility(visible = viewModel.installManager.current != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.clip(RoundedCornerShape(16.dp))
) {
SegmentedButton(
icon = Icons.Filled.OpenInNew,
text = stringResource(R.string.action_launch),
onClick = { viewModel.launchVendetta() }
)
SegmentedButton(
icon = Icons.Filled.Info,
text = stringResource(R.string.action_info),
onClick = { viewModel.launchVendettaInfo() }
)
SegmentedButton(
icon = Icons.Filled.Delete,
text = stringResource(R.string.action_uninstall),
onClick = { viewModel.uninstallVendetta() }
)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
}
}
}

View File

@ -3,7 +3,9 @@ package dev.beefers.vendetta.manager.ui.screen.installer
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -11,6 +13,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
@ -32,13 +35,19 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import dev.beefers.vendetta.manager.R
import dev.beefers.vendetta.manager.ui.viewmodel.installer.InstallerViewModel
import dev.beefers.vendetta.manager.ui.widgets.installer.StepGroupCard
import dev.beefers.vendetta.manager.utils.DiscordVersion
import org.koin.core.parameter.parametersOf
class InstallerScreen : Screen {
class InstallerScreen(
val version: DiscordVersion
) : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val viewModel: InstallerViewModel = getScreenModel()
val viewModel: InstallerViewModel = getScreenModel {
parametersOf(version)
}
var expandedGroup by remember {
mutableStateOf<InstallerViewModel.InstallStepGroup?>(null)
@ -65,20 +74,33 @@ class InstallerScreen : Screen {
steps = viewModel.getSteps(group),
)
}
if (viewModel.isFinished) {
Spacer(modifier = Modifier.height(16.dp))
viewModel.installManager.current?.let {
Button(
onClick = { viewModel.launchVendetta() },
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.action_launch))
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Button(
FilledTonalButton(
onClick = { viewModel.copyDebugInfo() },
modifier = Modifier.weight(1f)
) {
Text(stringResource(R.string.action_copy_logs))
}
Button(
FilledTonalButton(
onClick = { viewModel.clearCache() },
modifier = Modifier.weight(1f)
) {

View File

@ -79,6 +79,16 @@ class SettingsScreen : ManagerTab {
prefs.patchIcon = it
}
)
SettingsItemChoice(
label = stringResource(R.string.settings_channel),
pref = prefs.channel,
labelFactory = {
ctx.getString(it.labelRes)
},
onPrefChange = {
prefs.channel = it
}
)
SettingsButton(
label = stringResource(R.string.action_clear_cache),

View File

@ -0,0 +1,63 @@
package dev.beefers.vendetta.manager.ui.viewmodel.home
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import dev.beefers.vendetta.manager.domain.manager.InstallManager
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import dev.beefers.vendetta.manager.domain.repository.RestRepository
import dev.beefers.vendetta.manager.network.utils.dataOrNull
import dev.beefers.vendetta.manager.utils.DiscordVersion
import kotlinx.coroutines.launch
class HomeViewModel(
private val repo: RestRepository,
val context: Context,
val prefs: PreferenceManager,
val installManager: InstallManager
) : ScreenModel {
var discordVersions by mutableStateOf<Map<DiscordVersion.Type, DiscordVersion?>?>(null)
private set
init {
getDiscordVersions()
}
private fun getDiscordVersions() {
coroutineScope.launch {
discordVersions = repo.getLatestDiscordVersions().dataOrNull
}
}
fun launchVendetta() {
installManager.current?.let {
val intent = context.packageManager.getLaunchIntentForPackage(it.packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
fun uninstallVendetta() {
installManager.uninstall()
}
fun launchVendettaInfo() {
installManager.current?.let {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
data = Uri.parse("package:${it.packageName}")
context.startActivity(this)
}
}
}
}

View File

@ -1,6 +1,7 @@
package dev.beefers.vendetta.manager.ui.viewmodel.installer
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.StringRes
@ -17,11 +18,13 @@ import com.github.diamondminer88.zip.ZipWriter
import dev.beefers.vendetta.manager.BuildConfig
import dev.beefers.vendetta.manager.R
import dev.beefers.vendetta.manager.domain.manager.DownloadManager
import dev.beefers.vendetta.manager.domain.manager.InstallManager
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import dev.beefers.vendetta.manager.installer.util.ManifestPatcher
import dev.beefers.vendetta.manager.installer.util.Patcher
import dev.beefers.vendetta.manager.installer.util.installApks
import dev.beefers.vendetta.manager.network.utils.Signer
import dev.beefers.vendetta.manager.utils.DiscordVersion
import dev.beefers.vendetta.manager.utils.copyText
import dev.beefers.vendetta.manager.utils.showToast
import kotlinx.coroutines.Dispatchers
@ -36,7 +39,9 @@ import kotlin.time.measureTimedValue
class InstallerViewModel(
private val context: Context,
private val downloadManager: DownloadManager,
private val preferences: PreferenceManager
private val preferences: PreferenceManager,
private val discordVersion: DiscordVersion,
val installManager: InstallManager
) : ScreenModel {
private val installationRunning = AtomicBoolean(false)
private val cacheDir = context.externalCacheDir!!
@ -82,6 +87,15 @@ class InstallerViewModel(
context.showToast(R.string.msg_cleared_cache)
}
fun launchVendetta() {
installManager.current?.let {
val intent = context.packageManager.getLaunchIntentForPackage(it.packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
private val job = coroutineScope.launch(Dispatchers.Main) {
if (installationRunning.getAndSet(true)) {
return@launch
@ -131,7 +145,7 @@ class InstallerViewModel(
InstallStatus.QUEUED
)
val version = preferences.discordVersion.ifBlank { "168018" }
val version = preferences.discordVersion.ifBlank { discordVersion.toVersionCode() }
val arch = Build.SUPPORTED_ABIS.first()
val discordCacheDir = cacheDir.resolve(version)
val patchedDir = discordCacheDir.resolve("patched").also { it.deleteRecursively() }

View File

@ -7,7 +7,7 @@ import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import dev.beefers.vendetta.manager.domain.manager.DownloadManager
import dev.beefers.vendetta.manager.domain.repository.GithubRepository
import dev.beefers.vendetta.manager.domain.repository.RestRepository
import dev.beefers.vendetta.manager.installer.util.installApks
import dev.beefers.vendetta.manager.network.dto.Release
import dev.beefers.vendetta.manager.network.utils.dataOrNull
@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
import java.io.File
class MainViewModel(
private val githubRepo: GithubRepository,
private val repo: RestRepository,
private val downloadManager: DownloadManager,
private val context: Context
) : ScreenModel {
@ -29,7 +29,7 @@ class MainViewModel(
private fun checkForUpdate() {
coroutineScope.launch {
release = githubRepo.getLatestRelease().dataOrNull
release = repo.getLatestRelease().dataOrNull
}
}

View File

@ -20,7 +20,7 @@ fun UpdateDialog(
onDismissRequest = {},
confirmButton = {
FilledTonalButton(onClick = onConfirm) {
Text(stringResource(R.string.action_update))
Text(stringResource(R.string.action_start_update))
}
},
title = {

View File

@ -1,17 +1,25 @@
package dev.beefers.vendetta.manager.utils
import androidx.annotation.StringRes
import dev.beefers.vendetta.manager.R
import java.io.Serializable
data class DiscordVersion(
val major: Int,
val minor: Int,
val type: Type
) {
) : Serializable {
enum class Type {
STABLE,
BETA,
ALPHA
enum class Type(val label: String, @StringRes val labelRes: Int) {
STABLE("Stable", R.string.channel_stable),
BETA("Beta", R.string.channel_beta),
ALPHA("Alpha", R.string.channel_alpha)
}
override fun toString() = "$major.$minor - ${type.label}"
fun toVersionCode() = "$major${type.ordinal}${if (minor < 10) 0 else ""}${minor}"
companion object {
fun fromVersionCode(string: String): DiscordVersion? = with(string) {
@ -21,8 +29,8 @@ data class DiscordVersion(
val typeInt = codeReversed[2].toString().toInt()
val type = Type.values().getOrNull(typeInt) ?: return@with null
DiscordVersion(
codeReversed.slice(3..codeReversed.lastIndex).toInt(),
codeReversed.substring(0, 2).toInt(),
codeReversed.slice(3..codeReversed.lastIndex).reversed().toInt(),
codeReversed.substring(0, 2).reversed().toInt(),
type
)
}

View File

@ -0,0 +1,7 @@
<vector android:height="24dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M0,0h512v512h-512z"/>
<path android:fillColor="#FFFFFF" android:pathData="M366.27,150.56C346.2,141.35 324.68,134.57 302.17,130.68C301.77,130.61 301.36,130.79 301.15,131.16C298.38,136.08 295.31,142.51 293.16,147.56C268.97,143.94 244.89,143.94 221.18,147.56C219.03,142.4 215.85,136.08 213.08,131.16C212.86,130.8 212.46,130.62 212.04,130.68C189.55,134.55 168.03,141.34 147.96,150.56C147.77,150.63 147.63,150.76 147.53,150.92C106.71,211.91 95.53,271.4 101.01,330.15C101.03,330.43 101.19,330.7 101.42,330.88C128.36,350.66 154.45,362.67 180.05,370.62C180.46,370.75 180.9,370.6 181.15,370.26C187.2,362 192.6,353.28 197.24,344.1C197.51,343.56 197.25,342.92 196.69,342.71C188.13,339.46 179.98,335.5 172.13,331.01C171.51,330.65 171.46,329.75 172.03,329.33C173.68,328.1 175.34,326.8 176.9,325.5C177.19,325.27 177.59,325.23 177.93,325.38C229.46,348.9 285.24,348.9 336.17,325.38C336.51,325.2 336.9,325.26 337.2,325.49C338.77,326.79 340.43,328.1 342.09,329.33C342.66,329.75 342.63,330.65 342.01,331.01C334.16,335.59 326.01,339.46 317.42,342.7C316.87,342.91 316.63,343.56 316.89,344.1C321.62,353.26 327.02,361.98 332.97,370.25C333.21,370.6 333.66,370.75 334.08,370.62C359.8,362.67 385.89,350.66 412.83,330.88C413.06,330.7 413.21,330.45 413.23,330.16C419.8,262.24 402.24,203.23 366.67,150.93C366.59,150.76 366.44,150.63 366.27,150.56ZM204.93,294.37C189.41,294.37 176.64,280.13 176.64,262.63C176.64,245.14 189.17,230.9 204.93,230.9C220.82,230.9 233.48,245.27 233.23,262.63C233.23,280.13 220.7,294.37 204.93,294.37ZM309.56,294.37C294.05,294.37 281.26,280.13 281.26,262.63C281.26,245.14 293.79,230.9 309.56,230.9C325.45,230.9 338.11,245.27 337.86,262.63C337.86,280.13 325.45,294.37 309.56,294.37Z"/>
</group>
</vector>

View File

@ -35,8 +35,13 @@
<string name="action_copy_logs">Copy logs</string>
<string name="action_clear_cache">Clear cache</string>
<string name="action_confirm">Confirm</string>
<string name="action_update">Start update</string>
<string name="action_start_update">Start update</string>
<string name="action_install">Install</string>
<string name="action_update">Update</string>
<string name="action_reinstall">Reinstall</string>
<string name="action_launch">Launch</string>
<string name="action_uninstall">Uninstall</string>
<string name="action_info">Info</string>
<string name="installer_cached">Cached</string>
@ -58,6 +63,11 @@
<string name="settings_app_name">App name</string>
<string name="settings_app_icon">Replace app icon</string>
<string name="settings_app_icon_description">Uses the Vendetta icon instead of Discord\'s</string>
<string name="settings_channel">Release channel</string>
<string name="update_description">Vendetta Manager version %1$s is now available!</string>
<string name="channel_stable">Stable</string>
<string name="channel_beta">Beta</string>
<string name="channel_alpha">Alpha</string>
</resources>