Feat: Initial home ui + release channel option
This commit is contained in:
parent
92a650b067
commit
d6fa04f024
28 changed files with 491 additions and 59 deletions
6
.idea/GradleUpdaterPlugin.xml
Normal file
6
.idea/GradleUpdaterPlugin.xml
Normal 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>
|
|
@ -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>
|
|
@ -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()}\"")
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionCode": 1000,
|
||||
"versionName": "1.0.0",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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() }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
7
app/src/main/res/drawable/ic_discord_icon.xml
Normal file
7
app/src/main/res/drawable/ic_discord_icon.xml
Normal 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>
|
|
@ -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>
|
Loading…
Reference in a new issue