feat: move to voyager, clean up, remove NavBar
This commit is contained in:
parent
0efd525e88
commit
5339459ac8
55 changed files with 1106 additions and 1120 deletions
|
|
@ -1,4 +1,6 @@
|
|||
# Aliucord Manager [](https://crowdin.com/project/aliucord-manager)
|
||||
Aliucord Manager - Aliucord Installer rewrite in [Kotlin](https://kotlinlang.org) and [Jetpack Compose](https://developer.android.com/jetpack/compose).
|
||||
|
||||
Aliucord Manager - Aliucord Installer rewrite in [Kotlin](https://kotlinlang.org)
|
||||
and [Jetpack Compose](https://developer.android.com/jetpack/compose).
|
||||
|
||||
INFO: This app is not functional yet, if you want to install Aliucord - [read this](https://github.com/Aliucord/Aliucord#-installation)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,10 @@ android {
|
|||
jvmTarget = "11"
|
||||
freeCompilerArgs += listOf(
|
||||
"-Xcontext-receivers",
|
||||
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${reportsDir}",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
// "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${reportsDir}",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +113,7 @@ dependencies {
|
|||
implementation(libs.bundles.compose)
|
||||
implementation(libs.bundles.koin)
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.voyager)
|
||||
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
debugImplementation(libs.compose.ui.tooling)
|
||||
|
|
@ -122,7 +126,6 @@ dependencies {
|
|||
implementation(libs.axml)
|
||||
implementation(libs.bouncycastle)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.navigationReimagined)
|
||||
implementation(variantOf(libs.zip) { artifactType("aar") })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,17 +12,16 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.transitions.FadeTransition
|
||||
import com.aliucord.manager.domain.manager.PreferencesManager
|
||||
import com.aliucord.manager.ui.dialog.StoragePermissionsDialog
|
||||
import com.aliucord.manager.ui.dialog.UpdaterDialog
|
||||
import com.aliucord.manager.ui.navigation.*
|
||||
import com.aliucord.manager.ui.screen.*
|
||||
import com.aliucord.manager.ui.theme.ManagerTheme
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import dev.olshevski.navigation.reimagined.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.aliucord.manager.ui.components.*
|
||||
import com.aliucord.manager.ui.components.dialogs.StoragePermissionsDialog
|
||||
import com.aliucord.manager.ui.screens.home.HomeScreen
|
||||
import com.aliucord.manager.ui.widgets.updater.UpdaterDialog
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
// TODO: move to a path provider in DI
|
||||
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
|
@ -38,12 +37,6 @@ class MainActivity : ComponentActivity() {
|
|||
isDarkTheme = preferences.theme == Theme.DARK || preferences.theme == Theme.SYSTEM && isSystemInDarkTheme(),
|
||||
isDynamicColor = preferences.dynamicColor
|
||||
) {
|
||||
val navController = rememberNavController<AppDestination>(AppDestination.Home)
|
||||
|
||||
BackHandler {
|
||||
navController.back()
|
||||
}
|
||||
|
||||
StoragePermissionsDialog()
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
|
|
@ -55,36 +48,15 @@ class MainActivity : ComponentActivity() {
|
|||
UpdaterDialog()
|
||||
}
|
||||
|
||||
NavHost(
|
||||
controller = navController,
|
||||
) {
|
||||
when (val dest = this.currentHostEntry.destination) {
|
||||
is BaseScreenDestination -> BaseScreen(
|
||||
currentNavItem = dest,
|
||||
bottomNavItems = persistentListOf(AppDestination.Home, AppDestination.Plugins, AppDestination.Settings),
|
||||
onNavChanged = { navController.replaceLast(it) }
|
||||
) {
|
||||
when (dest) {
|
||||
is AppDestination.Home -> HomeScreen(
|
||||
onClickInstall = { data ->
|
||||
navController.navigate(AppDestination.Install(data))
|
||||
}
|
||||
)
|
||||
|
||||
is AppDestination.Plugins -> PluginsScreen()
|
||||
is AppDestination.Settings -> SettingsScreen()
|
||||
}
|
||||
}
|
||||
|
||||
is AppDestination.Install -> InstallerScreen(
|
||||
installData = dest.installData,
|
||||
onBackClick = { navController.back() }
|
||||
)
|
||||
|
||||
is AppDestination.About -> AboutScreen(
|
||||
onBackClick = { navController.back() }
|
||||
)
|
||||
Navigator(
|
||||
screen = HomeScreen(),
|
||||
onBackPressed = null,
|
||||
) { navigator ->
|
||||
BackHandler {
|
||||
navigator.back(this@MainActivity)
|
||||
}
|
||||
|
||||
FadeTransition(navigator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,16 @@ import com.aliucord.manager.di.*
|
|||
import com.aliucord.manager.domain.repository.AliucordMavenRepository
|
||||
import com.aliucord.manager.domain.repository.GithubRepository
|
||||
import com.aliucord.manager.network.service.*
|
||||
import com.aliucord.manager.ui.viewmodel.*
|
||||
import com.aliucord.manager.ui.screens.about.AboutModel
|
||||
import com.aliucord.manager.ui.screens.home.HomeModel
|
||||
import com.aliucord.manager.ui.screens.install.InstallModel
|
||||
import com.aliucord.manager.ui.screens.plugins.PluginsModel
|
||||
import com.aliucord.manager.ui.screens.settings.SettingsModel
|
||||
import com.aliucord.manager.ui.widgets.updater.UpdaterViewModel
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
|
@ -39,13 +45,13 @@ class ManagerApplication : Application() {
|
|||
singleOf(::AliucordMavenRepository)
|
||||
})
|
||||
|
||||
// ViewModels
|
||||
// UI Models
|
||||
modules(module {
|
||||
viewModelOf(::HomeViewModel)
|
||||
viewModelOf(::PluginsViewModel)
|
||||
viewModelOf(::AboutViewModel)
|
||||
viewModelOf(::InstallViewModel)
|
||||
viewModelOf(::SettingsViewModel)
|
||||
factoryOf(::HomeModel)
|
||||
factoryOf(::PluginsModel)
|
||||
factoryOf(::AboutModel)
|
||||
factoryOf(::InstallModel)
|
||||
factoryOf(::SettingsModel)
|
||||
viewModelOf(::UpdaterViewModel)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import java.io.File
|
|||
import kotlin.coroutines.*
|
||||
|
||||
class DownloadManager(
|
||||
private val application: Application
|
||||
private val application: Application,
|
||||
) {
|
||||
private val downloadManager = application.getSystemService<DownloadManager>()!!
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.aliucord.manager.domain.manager
|
|||
|
||||
import android.content.SharedPreferences
|
||||
import com.aliucord.manager.domain.manager.base.BasePreferenceManager
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import com.aliucord.manager.ui.components.Theme
|
||||
|
||||
class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager(preferences) {
|
||||
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.core.content.edit
|
|||
import kotlin.reflect.KProperty
|
||||
|
||||
abstract class BasePreferenceManager(
|
||||
private val prefs: SharedPreferences
|
||||
private val prefs: SharedPreferences,
|
||||
) {
|
||||
protected fun getString(key: String, defaultValue: String) = prefs.getString(key, defaultValue) ?: defaultValue
|
||||
private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue)
|
||||
|
|
@ -25,7 +25,7 @@ abstract class BasePreferenceManager(
|
|||
private val key: String,
|
||||
defaultValue: T,
|
||||
getter: (key: String, defaultValue: T) -> T,
|
||||
private val setter: (key: String, newValue: T) -> Unit
|
||||
private val setter: (key: String, newValue: T) -> Unit,
|
||||
) {
|
||||
@Suppress("RedundantSetter")
|
||||
var value by mutableStateOf(getter(key, defaultValue))
|
||||
|
|
@ -40,7 +40,7 @@ abstract class BasePreferenceManager(
|
|||
|
||||
protected fun stringPreference(
|
||||
key: String,
|
||||
defaultValue: String
|
||||
defaultValue: String,
|
||||
) = Preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
|
|
@ -50,7 +50,7 @@ abstract class BasePreferenceManager(
|
|||
|
||||
protected fun booleanPreference(
|
||||
key: String,
|
||||
defaultValue: Boolean
|
||||
defaultValue: Boolean,
|
||||
) = Preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
|
|
@ -60,7 +60,7 @@ abstract class BasePreferenceManager(
|
|||
|
||||
protected fun intPreference(
|
||||
key: String,
|
||||
defaultValue: Int
|
||||
defaultValue: Int,
|
||||
) = Preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
|
|
@ -70,7 +70,7 @@ abstract class BasePreferenceManager(
|
|||
|
||||
protected fun floatPreference(
|
||||
key: String,
|
||||
defaultValue: Float
|
||||
defaultValue: Float,
|
||||
) = Preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
|
|
@ -80,7 +80,7 @@ abstract class BasePreferenceManager(
|
|||
|
||||
protected inline fun <reified E : Enum<E>> enumPreference(
|
||||
key: String,
|
||||
defaultValue: E
|
||||
defaultValue: E,
|
||||
) = Preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ import com.github.diamondminer88.zip.ZipReader
|
|||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import java.io.File
|
||||
|
||||
private val json: Json = GlobalContext.get().get()
|
||||
|
||||
@Immutable
|
||||
data class Plugin(val file: File) {
|
||||
data class Plugin(val file: File) : KoinComponent {
|
||||
val manifest: Manifest
|
||||
|
||||
init {
|
||||
|
|
@ -25,7 +24,7 @@ data class Plugin(val file: File) {
|
|||
?: throw Exception("Plugin ${file.nameWithoutExtension} has no manifest.")
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
this.manifest = json.decodeFromStream(manifest.read().inputStream())
|
||||
this.manifest = get<Json>().decodeFromStream(manifest.read().inputStream())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.aliucord.manager.network.utils.*
|
|||
import io.ktor.http.HttpStatusCode
|
||||
|
||||
class AliucordMavenRepository(
|
||||
private val maven: MavenService
|
||||
private val maven: MavenService,
|
||||
) {
|
||||
suspend fun getAliuhookVersion(): ApiResponse<String> {
|
||||
return maven.getArtifactMetadata(BASE_URL, ALIUHOOK).transform {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.aliucord.manager.network.service.AliucordGithubService
|
|||
import com.aliucord.manager.network.utils.transform
|
||||
|
||||
class GithubRepository(
|
||||
private val service: AliucordGithubService
|
||||
private val service: AliucordGithubService,
|
||||
) {
|
||||
suspend fun getCommits(page: Int = 0) = service.getCommits(page)
|
||||
suspend fun getContributors() = service.getContributors()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ object ManifestPatcher {
|
|||
manifestBytes: ByteArray,
|
||||
packageName: String,
|
||||
appName: String,
|
||||
debuggable: Boolean
|
||||
debuggable: Boolean,
|
||||
): ByteArray {
|
||||
val reader = AxmlReader(manifestBytes)
|
||||
val writer = AxmlWriter()
|
||||
|
|
@ -142,7 +142,7 @@ object ManifestPatcher {
|
|||
|
||||
fun renamePackage(
|
||||
manifestBytes: ByteArray,
|
||||
packageName: String
|
||||
packageName: String,
|
||||
): ByteArray {
|
||||
val reader = AxmlReader(manifestBytes)
|
||||
val writer = AxmlWriter()
|
||||
|
|
@ -160,7 +160,7 @@ object ManifestPatcher {
|
|||
|
||||
private open class ReplaceAttrsVisitor(
|
||||
nv: NodeVisitor,
|
||||
private val attrs: Map<String, Any>
|
||||
private val attrs: Map<String, Any>,
|
||||
) : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
val replace = attrs.containsKey(name)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ data class Commit(
|
|||
val htmlUrl: String,
|
||||
val sha: String,
|
||||
val commit: Commit,
|
||||
val author: Author?
|
||||
val author: Author?,
|
||||
) {
|
||||
@Serializable
|
||||
data class Author(@SerialName("login") val name: String)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ data class GithubRelease(
|
|||
val createdAt: String,
|
||||
val assets: List<GithubReleaseAssets>,
|
||||
@SerialName("tag_name")
|
||||
val tagName: String
|
||||
val tagName: String,
|
||||
) {
|
||||
@Serializable
|
||||
data class GithubReleaseAssets(
|
||||
val name: String,
|
||||
@SerialName("browser_download_url")
|
||||
val browserDownloadUrl: String
|
||||
val browserDownloadUrl: String,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ import kotlinx.serialization.Serializable
|
|||
data class GithubUser(
|
||||
@SerialName("login")
|
||||
val name: String,
|
||||
val contributions: Int
|
||||
val contributions: Int,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ import kotlinx.serialization.Serializable
|
|||
data class Version(
|
||||
val versionCode: String,
|
||||
val versionName: String,
|
||||
val aliucordHash: String
|
||||
val aliucordHash: String,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext
|
|||
|
||||
class AliucordGithubService(
|
||||
val github: GithubService,
|
||||
val http: HttpService
|
||||
val http: HttpService,
|
||||
) {
|
||||
suspend fun getDataJson(): ApiResponse<Version> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ApiFailure(error: Throwable, body: String?) : Error(body, error)
|
|||
inline fun <T, R> ApiResponse<T>.fold(
|
||||
success: (T) -> R,
|
||||
error: (ApiError) -> R,
|
||||
failure: (ApiFailure) -> R
|
||||
failure: (ApiFailure) -> R,
|
||||
): R {
|
||||
return when (this) {
|
||||
is ApiResponse.Success -> success(this.data)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ data class SemVer(
|
|||
val major: Int,
|
||||
val minor: Int,
|
||||
val patch: Int,
|
||||
private val vPrefix: Boolean = false
|
||||
private val vPrefix: Boolean = false,
|
||||
) : Comparable<SemVer> {
|
||||
override fun compareTo(other: SemVer): Int {
|
||||
val pairs = arrayOf(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.aliucord.manager.ui.components
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.screens.home.HomeScreen
|
||||
|
||||
/**
|
||||
* Standalone back button for interacting with the current navigator.
|
||||
*/
|
||||
@Composable
|
||||
fun BackButton() {
|
||||
// Support rendering in @Preview
|
||||
val context = if (LocalInspectionMode.current) null else LocalContext.current as Activity
|
||||
val navigator = LocalNavigator.current
|
||||
|
||||
IconButton(
|
||||
onClick = { if (context != null) navigator?.back(context) },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back),
|
||||
contentDescription = stringResource(R.string.navigation_back),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom back logic for handling special screens differently.
|
||||
* If on Home, Landing, or Debug then exit immediately otherwise go back one.
|
||||
*/
|
||||
fun Navigator.back(currentActivity: Activity?) {
|
||||
val top = this.lastItemOrNull ?: return
|
||||
val stackSize = this.items.size
|
||||
|
||||
if (stackSize > 1) {
|
||||
pop()
|
||||
} else if (top is HomeScreen) {
|
||||
currentActivity?.finish()
|
||||
} else {
|
||||
replaceAll(HomeScreen())
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,13 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.component
|
||||
package com.aliucord.manager.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.*
|
||||
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
|
||||
|
|
@ -22,41 +21,15 @@ import coil.compose.AsyncImage
|
|||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.network.dto.GithubUser
|
||||
|
||||
fun LazyListScope.contributors(contributors: List<GithubUser>) {
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.contributors),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (contributors.isNotEmpty()) {
|
||||
items(contributors) { user ->
|
||||
ContributorEntry(user = user)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = { CircularProgressIndicator() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContributorEntry(user: GithubUser) {
|
||||
fun ContributorCommitsItem(
|
||||
user: GithubUser,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.clickable {
|
||||
uriHandler.openUri("https://github.com/${user.name}")
|
||||
}
|
||||
|
|
@ -85,6 +58,5 @@ fun ContributorEntry(user: GithubUser) {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component
|
||||
package com.aliucord.manager.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.theme
|
||||
package com.aliucord.manager.ui.components
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
|
|
@ -19,7 +19,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|||
fun ManagerTheme(
|
||||
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
isDynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
val colorScheme = when {
|
||||
|
|
@ -3,18 +3,18 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.dialog
|
||||
package com.aliucord.manager.ui.components.dialogs
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import com.aliucord.manager.ui.screen.InstallData
|
||||
import com.aliucord.manager.ui.screens.install.InstallData
|
||||
|
||||
enum class DownloadMethod { DOWNLOAD }
|
||||
|
||||
@Composable
|
||||
fun InstallerDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (InstallData) -> Unit
|
||||
onConfirm: (InstallData) -> Unit,
|
||||
) {
|
||||
val downloadMethod by rememberSaveable { mutableStateOf(DownloadMethod.DOWNLOAD) }
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.dialog
|
||||
package com.aliucord.manager.ui.components.dialogs
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
|
|
@ -7,6 +7,7 @@ import android.os.Environment
|
|||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -26,6 +27,7 @@ fun StoragePermissionsDialog() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun ManageStorageDialog() {
|
||||
var manageStorageGranted by remember { mutableStateOf(Environment.isExternalStorageManager()) }
|
||||
|
||||
|
|
@ -59,8 +61,8 @@ private fun ManageStorageDialog() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
private fun ExternalStorageDialog() {
|
||||
val writeStorageState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.home
|
||||
package com.aliucord.manager.ui.components.home
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -22,7 +22,7 @@ import androidx.paging.compose.items
|
|||
import coil.compose.AsyncImage
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.network.dto.Commit
|
||||
import com.aliucord.manager.ui.component.LoadFailure
|
||||
import com.aliucord.manager.ui.components.LoadFailure
|
||||
import com.aliucord.manager.ui.util.annotatingStringResource
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
@Composable
|
||||
fun CommitList(
|
||||
commits: Flow<PagingData<Commit>>,
|
||||
onRetry: () -> Unit
|
||||
onRetry: () -> Unit,
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
|
|
@ -113,7 +113,7 @@ fun CommitList(
|
|||
|
||||
@Composable
|
||||
fun CommitItem(
|
||||
commit: Commit
|
||||
commit: Commit,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.home
|
||||
package com.aliucord.manager.ui.components.home
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
|
@ -23,7 +23,7 @@ fun InfoCard(
|
|||
currentVersion: DiscordVersion,
|
||||
onDownloadClick: () -> Unit,
|
||||
onUninstallClick: () -> Unit,
|
||||
onLaunchClick: () -> Unit
|
||||
onLaunchClick: () -> Unit,
|
||||
) {
|
||||
ElevatedCard {
|
||||
Column(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.installer
|
||||
package com.aliucord.manager.ui.components.installer
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -35,7 +35,7 @@ class InstallStepData(
|
|||
val nameResId: Int,
|
||||
status: InstallStatus,
|
||||
duration: Float = 0f,
|
||||
cached: Boolean = false
|
||||
cached: Boolean = false,
|
||||
) {
|
||||
var status by mutableStateOf(status)
|
||||
var duration by mutableStateOf(duration)
|
||||
|
|
@ -47,7 +47,7 @@ fun InstallGroup(
|
|||
name: String,
|
||||
isCurrent: Boolean,
|
||||
subSteps: ImmutableList<InstallStepData>,
|
||||
onClick: () -> Unit
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val status = when {
|
||||
subSteps.all { it.status == InstallStatus.QUEUED } ->
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.component.plugins
|
||||
package com.aliucord.manager.ui.components.plugins
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -64,7 +64,7 @@ private fun AnnotatedString.Builder.hyperlink(content: String) {
|
|||
@Composable
|
||||
fun Changelog(
|
||||
plugin: Plugin,
|
||||
onDismiss: () -> Unit
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
|
|
@ -164,7 +164,7 @@ fun Changelog(
|
|||
@Composable
|
||||
private fun LinkText(
|
||||
modifier: Modifier = Modifier,
|
||||
annotatedString: AnnotatedString
|
||||
annotatedString: AnnotatedString,
|
||||
) {
|
||||
val urlHandler = LocalUriHandler.current
|
||||
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.component.plugins
|
||||
package com.aliucord.manager.ui.components.plugins
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.settings
|
||||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp
|
|||
|
||||
@Composable
|
||||
fun SettingsHeader(
|
||||
text: String
|
||||
text: String,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.settings
|
||||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.component.settings
|
||||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material3.Switch
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.aliucord.manager.ui.component.settings
|
||||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsTextField(
|
||||
label: String,
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package com.aliucord.manager.ui.navigation
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.screen.InstallData
|
||||
import dev.olshevski.navigation.reimagined.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
sealed class BaseScreenDestination(
|
||||
@DrawableRes val selectedIcon: Int,
|
||||
@DrawableRes val unselectedIcon: Int,
|
||||
@StringRes val label: Int
|
||||
) : AppDestination
|
||||
|
||||
@Parcelize
|
||||
sealed interface AppDestination : Parcelable {
|
||||
@Parcelize
|
||||
object Home : BaseScreenDestination(
|
||||
R.drawable.ic_home,
|
||||
R.drawable.ic_home, // TODO: get ic_home outline variant
|
||||
R.string.navigation_home
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
object Plugins : BaseScreenDestination(
|
||||
R.drawable.ic_extension,
|
||||
R.drawable.ic_extension_outlined,
|
||||
R.string.plugins_title
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
object Settings : BaseScreenDestination(
|
||||
R.drawable.ic_settings,
|
||||
R.drawable.ic_settings_outlined,
|
||||
R.string.navigation_settings
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Install(val installData: InstallData) : AppDestination
|
||||
|
||||
@Parcelize
|
||||
object About : AppDestination
|
||||
}
|
||||
|
||||
context(Activity)
|
||||
fun NavController<AppDestination>.back() {
|
||||
val topDest = backstack.entries.lastOrNull()?.destination
|
||||
|
||||
if (topDest == AppDestination.Home) {
|
||||
finish()
|
||||
} else if (topDest is BaseScreenDestination) {
|
||||
replaceAll(AppDestination.Home)
|
||||
} else if (backstack.entries.size > 1) {
|
||||
pop()
|
||||
} else {
|
||||
replaceAll(AppDestination.Home)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package com.aliucord.manager.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun BaseScreen(
|
||||
currentNavItem: BaseScreenDestination,
|
||||
bottomNavItems: ImmutableList<BaseScreenDestination>,
|
||||
onNavChanged: (AppDestination) -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(currentNavItem.label)) },
|
||||
actions = {
|
||||
val localUriHandler = LocalUriHandler.current
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
localUriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}")
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_discord),
|
||||
contentDescription = stringResource(R.string.support_server)
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = { onNavChanged(AppDestination.About) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = stringResource(R.string.navigation_about)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
for (navItem in bottomNavItems) {
|
||||
NavigationBarItem(
|
||||
selected = currentNavItem == navItem,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = if (currentNavItem == navItem) {
|
||||
painterResource(navItem.selectedIcon)
|
||||
} else {
|
||||
painterResource(navItem.unselectedIcon)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(navItem.label)) },
|
||||
onClick = { onNavChanged(navItem) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.component.LoadFailure
|
||||
import com.aliucord.manager.ui.component.contributors
|
||||
import com.aliucord.manager.ui.viewmodel.AboutViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AboutScreen(
|
||||
viewModel: AboutViewModel = getViewModel(),
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.navigation_about)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back),
|
||||
contentDescription = stringResource(R.string.navigation_back)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "https://github.com/Aliucord.png",
|
||||
contentDescription = stringResource(R.string.aliucord),
|
||||
modifier = Modifier.size(71.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.aliucord),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 26.sp
|
||||
)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.app_description),
|
||||
style = MaterialTheme.typography.titleSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = { uriHandler.openUri("https://github.com/Aliucord") }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_account_github_white_24dp),
|
||||
contentDescription = stringResource(R.string.github)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
|
||||
Text(text = stringResource(id = R.string.github))
|
||||
}
|
||||
|
||||
TextButton(onClick = { uriHandler.openUri("https://aliucord.com") }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_link),
|
||||
contentDescription = stringResource(R.string.website)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
|
||||
Text(text = stringResource(id = R.string.website))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!viewModel.fetchError) {
|
||||
item {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, bottom = 20.dp)
|
||||
) {
|
||||
UserEntry("Vendicated", "the ven")
|
||||
UserEntry("Juby210", "Fox")
|
||||
UserEntry("rushii", "explod", "DiamondMiner88")
|
||||
}
|
||||
}
|
||||
|
||||
contributors(viewModel.contributors)
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.padding(top = 12.dp)
|
||||
) {
|
||||
LoadFailure(onRetry = { viewModel.fetchContributors() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserEntry(name: String, roles: String, username: String = name) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = { uriHandler.openUri("https://github.com/$username") },
|
||||
indication = rememberRipple(bounded = false, radius = 90.dp),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
.widthIn(min = 100.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.size(71.dp)
|
||||
.clip(CircleShape),
|
||||
model = "https://github.com/$username.png",
|
||||
contentDescription = username
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 18.sp
|
||||
)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = roles,
|
||||
style = MaterialTheme.typography.titleSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
|
||||
package com.aliucord.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.ui.component.home.CommitList
|
||||
import com.aliucord.manager.ui.component.home.InfoCard
|
||||
import com.aliucord.manager.ui.dialog.InstallerDialog
|
||||
import com.aliucord.manager.ui.viewmodel.HomeViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
viewModel: HomeViewModel = getViewModel(),
|
||||
onClickInstall: (InstallData) -> Unit
|
||||
) {
|
||||
var showInstallerDialog by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(viewModel.preferences.packageName) {
|
||||
viewModel.fetchInstalledVersion()
|
||||
}
|
||||
|
||||
if (showInstallerDialog) {
|
||||
InstallerDialog(
|
||||
onDismiss = { showInstallerDialog = false },
|
||||
onConfirm = { data ->
|
||||
showInstallerDialog = false
|
||||
onClickInstall(data)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
InfoCard(
|
||||
packageName = viewModel.preferences.packageName,
|
||||
supportedVersion = viewModel.supportedVersion,
|
||||
currentVersion = viewModel.installedVersion,
|
||||
onDownloadClick = { showInstallerDialog = true },
|
||||
onLaunchClick = viewModel::launchAliucord,
|
||||
onUninstallClick = viewModel::uninstallAliucord
|
||||
)
|
||||
|
||||
CommitList(
|
||||
commits = viewModel.commits,
|
||||
onRetry = { viewModel.fetchSupportedVersion() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screen
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.component.installer.InstallGroup
|
||||
import com.aliucord.manager.ui.component.installer.InstallStatus
|
||||
import com.aliucord.manager.ui.dialog.DownloadMethod
|
||||
import com.aliucord.manager.ui.viewmodel.InstallViewModel
|
||||
import com.aliucord.manager.ui.viewmodel.InstallViewModel.InstallStepGroup
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Immutable // this isn't *really* stable, but this never gets modified after being passed to a composable, so...
|
||||
@Parcelize
|
||||
data class InstallData(
|
||||
val downloadMethod: DownloadMethod,
|
||||
var baseApk: String? = null,
|
||||
var splits: List<String>? = null
|
||||
) : Parcelable
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun InstallerScreen(
|
||||
installData: InstallData,
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: InstallViewModel = getViewModel(parameters = { parametersOf(installData) })
|
||||
) {
|
||||
if (viewModel.returnToHome) onBackClick()
|
||||
|
||||
var expandedGroup by remember { mutableStateOf<InstallStepGroup?>(null) }
|
||||
|
||||
LaunchedEffect(viewModel.currentStep) {
|
||||
expandedGroup = viewModel.currentStep?.group
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.installer)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = stringResource(R.string.navigation_back)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(Modifier.padding(paddingValues)) {
|
||||
if (viewModel.steps[viewModel.currentStep]?.status == InstallStatus.ONGOING) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.padding(bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
for (group in InstallStepGroup.entries) {
|
||||
InstallGroup(
|
||||
name = stringResource(group.nameResId),
|
||||
isCurrent = expandedGroup == group,
|
||||
onClick = { expandedGroup = group },
|
||||
subSteps = viewModel.getSteps(group).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.isFinished && viewModel.stacktrace.isEmpty()) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalButton(onClick = viewModel::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.stacktrace.isNotEmpty()) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = viewModel.stacktrace,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
softWrap = false,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp))
|
||||
.padding(10.dp)
|
||||
.horizontalScroll(rememberScrollState())
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalButton(onClick = viewModel::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
OutlinedButton(onClick = viewModel::saveDebugToFile) {
|
||||
Text(stringResource(R.string.installer_save_file))
|
||||
}
|
||||
|
||||
FilledTonalButton(onClick = viewModel::copyDebugToClipboard) {
|
||||
Text(stringResource(R.string.action_copy))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.component.plugins.Changelog
|
||||
import com.aliucord.manager.ui.component.plugins.PluginCard
|
||||
import com.aliucord.manager.ui.viewmodel.PluginsViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PluginsScreen(
|
||||
viewModel: PluginsViewModel = getViewModel()
|
||||
) {
|
||||
if (viewModel.showUninstallDialog) {
|
||||
val plugin = viewModel.selectedPlugin!!
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = viewModel::hideUninstallDialog,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(R.string.action_uninstall)
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text("${stringResource(R.string.action_uninstall)} ${plugin.manifest.name}")
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.plugins_delete_plugin_body))
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
viewModel.uninstallPlugin(plugin)
|
||||
viewModel.hideUninstallDialog()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = viewModel::hideUninstallDialog,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.showChangelogDialog) {
|
||||
Changelog(
|
||||
plugin = viewModel.selectedPlugin!!,
|
||||
onDismiss = viewModel::hideChangelogDialog
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
value = viewModel.search,
|
||||
onValueChange = viewModel::search,
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
label = { Text(stringResource(R.string.action_search)) },
|
||||
trailingIcon = {
|
||||
if (viewModel.search.isNotBlank()) {
|
||||
IconButton(onClick = viewModel::clearSearch) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.action_clear)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(R.string.plugins_search)
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions { focusManager.clearFocus() }
|
||||
)
|
||||
|
||||
if (viewModel.error) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_error),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (viewModel.plugins.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 15.dp, top = 6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
items(
|
||||
viewModel.plugins.filter { plugin ->
|
||||
plugin.manifest.run {
|
||||
name.contains(viewModel.search, true)
|
||||
|| description.contains(viewModel.search, true)
|
||||
|| authors.any { (name) -> name.contains(viewModel.search, true) }
|
||||
}
|
||||
}
|
||||
) { plugin ->
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
enabled = viewModel.enabled[plugin.manifest.name] ?: true,
|
||||
onClickDelete = { viewModel.showUninstallDialog(plugin) },
|
||||
onClickShowChangelog = { viewModel.showChangelogDialog(plugin) },
|
||||
onSetEnabled = { viewModel.setPluginEnabled(plugin.manifest.name, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_extension_off),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.plugins_none_installed))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screen
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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 com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.component.settings.*
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import com.aliucord.manager.ui.viewmodel.SettingsViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = getViewModel()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
) {
|
||||
val preferences = viewModel.preferences
|
||||
|
||||
if (viewModel.showThemeDialog) {
|
||||
ThemeDialog(
|
||||
currentTheme = preferences.theme,
|
||||
onDismissRequest = viewModel::hideThemeDialog,
|
||||
onConfirm = viewModel::setTheme
|
||||
)
|
||||
}
|
||||
|
||||
SettingsHeader(stringResource(R.string.settings_appearance))
|
||||
|
||||
SettingsItem(
|
||||
modifier = Modifier.clickable(onClick = viewModel::showThemeDialog),
|
||||
icon = { Icon(painterResource(R.drawable.ic_brush), null) },
|
||||
text = { Text(stringResource(R.string.settings_theme)) }
|
||||
) {
|
||||
FilledTonalButton(onClick = viewModel::showThemeDialog) {
|
||||
Text(preferences.theme.toDisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_dynamic_color),
|
||||
pref = preferences.dynamicColor,
|
||||
icon = { Icon(painterResource(R.drawable.ic_palette), null) }
|
||||
) {
|
||||
preferences.dynamicColor = it
|
||||
}
|
||||
|
||||
SettingsHeader(stringResource(R.string.settings_advanced))
|
||||
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_app_name),
|
||||
pref = preferences.appName,
|
||||
onPrefChange = viewModel::setAppName
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_replace_icon),
|
||||
secondaryLabel = stringResource(R.string.setting_replace_icon_desc),
|
||||
pref = preferences.replaceIcon,
|
||||
icon = { Icon(painterResource(R.drawable.ic_app_shortcut), null) }
|
||||
) {
|
||||
preferences.replaceIcon = it
|
||||
}
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_keep_patched_apks),
|
||||
secondaryLabel = stringResource(R.string.setting_keep_patched_apks_desc),
|
||||
icon = { Icon(painterResource(R.drawable.ic_delete_forever), null) },
|
||||
pref = preferences.keepPatchedApks,
|
||||
onPrefChange = { preferences.keepPatchedApks = it },
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.settings_developer_options),
|
||||
pref = preferences.devMode,
|
||||
icon = { Icon(painterResource(R.drawable.ic_code), null) }
|
||||
) {
|
||||
preferences.devMode = it
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = preferences.devMode,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically()
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_package_name),
|
||||
pref = preferences.packageName,
|
||||
onPrefChange = viewModel::setPackageName,
|
||||
error = viewModel.packageNameError
|
||||
)
|
||||
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_target_version),
|
||||
pref = preferences.version,
|
||||
onPrefChange = viewModel::setVersion
|
||||
)
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_debuggable),
|
||||
secondaryLabel = stringResource(R.string.setting_debuggable_desc),
|
||||
pref = preferences.debuggable,
|
||||
icon = { Icon(painterResource(R.drawable.ic_bug), null) },
|
||||
onPrefChange = { preferences.debuggable = it },
|
||||
)
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_hermes_replace_libcpp),
|
||||
secondaryLabel = stringResource(R.string.setting_hermes_replace_libcpp_desc),
|
||||
icon = { Icon(painterResource(R.drawable.ic_copy_file), null) },
|
||||
pref = preferences.hermesReplaceLibCpp,
|
||||
onPrefChange = { preferences.hermesReplaceLibCpp = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(18.dp),
|
||||
shape = ShapeDefaults.Large,
|
||||
onClick = viewModel::clearCacheDir
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.setting_clear_cache),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThemeDialog(
|
||||
currentTheme: Theme,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (Theme) -> Unit
|
||||
) {
|
||||
var selectedTheme by rememberSaveable { mutableStateOf(currentTheme) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_brush),
|
||||
contentDescription = stringResource(R.string.settings_theme)
|
||||
)
|
||||
},
|
||||
title = { Text(stringResource(R.string.settings_theme)) },
|
||||
text = {
|
||||
Column {
|
||||
Theme.values().forEach { theme ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { selectedTheme = theme }
|
||||
.padding(start = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = theme.toDisplayName(),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
RadioButton(
|
||||
selected = theme == selectedTheme,
|
||||
onClick = { selectedTheme = theme }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
onConfirm(selectedTheme)
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.action_apply))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.screens.about
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.domain.repository.GithubRepository
|
||||
import com.aliucord.manager.network.dto.GithubUser
|
||||
import com.aliucord.manager.network.utils.fold
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AboutViewModel(
|
||||
private val githubRepository: GithubRepository
|
||||
) : ViewModel() {
|
||||
class AboutModel(
|
||||
private val githubRepository: GithubRepository,
|
||||
) : ScreenModel {
|
||||
val contributors = mutableStateListOf<GithubUser>()
|
||||
var fetchError by mutableStateOf(false)
|
||||
|
||||
|
|
@ -19,13 +19,14 @@ class AboutViewModel(
|
|||
}
|
||||
|
||||
fun fetchContributors() {
|
||||
viewModelScope.launch {
|
||||
screenModelScope.launch {
|
||||
githubRepository.getContributors().fold(
|
||||
success = {
|
||||
contributors.clear()
|
||||
contributors.addAll(it)
|
||||
fetchError = false
|
||||
},
|
||||
fail = { fetchError = true }
|
||||
fail = { fetchError = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.about
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.*
|
||||
|
||||
class AboutScreen : Screen {
|
||||
override val key = "About"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = getScreenModel<AboutModel>()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.navigation_about)) },
|
||||
navigationIcon = { BackButton() },
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
) {
|
||||
item(key = "PROJECT_HEADER") {
|
||||
ProjectHeader()
|
||||
}
|
||||
|
||||
if (!model.fetchError) {
|
||||
item(key = "MAIN_CONTRIBUTORS") {
|
||||
MainContributors()
|
||||
}
|
||||
|
||||
item("CONTRIBUTORS_HEADER") {
|
||||
Text(
|
||||
text = stringResource(R.string.contributors),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (model.contributors.isNotEmpty()) {
|
||||
items(model.contributors, key = { "user-${it.name}" }) { user ->
|
||||
ContributorCommitsItem(user)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = { CircularProgressIndicator() }
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item(key = "LOAD_FAILURE") {
|
||||
Box(
|
||||
modifier = Modifier.padding(top = 12.dp)
|
||||
) {
|
||||
LoadFailure(onRetry = { model.fetchContributors() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProjectHeader(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "https://github.com/Aliucord.png",
|
||||
contentDescription = stringResource(R.string.aliucord),
|
||||
modifier = Modifier.size(71.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.aliucord),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 26.sp
|
||||
)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.app_description),
|
||||
style = MaterialTheme.typography.titleSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
TextButton(onClick = { uriHandler.openUri("https://github.com/Aliucord") }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_account_github_white_24dp),
|
||||
contentDescription = stringResource(R.string.github)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
|
||||
Text(text = stringResource(id = R.string.github))
|
||||
}
|
||||
|
||||
TextButton(onClick = { uriHandler.openUri("https://aliucord.com") }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_link),
|
||||
contentDescription = stringResource(R.string.website)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
|
||||
Text(text = stringResource(id = R.string.website))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContributors(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, bottom = 20.dp)
|
||||
) {
|
||||
UserEntry("Vendicated", "the ven")
|
||||
UserEntry("Juby210", "Fox")
|
||||
UserEntry("rushii", "explod", "DiamondMiner88")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserEntry(name: String, roles: String, username: String = name) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = { uriHandler.openUri("https://github.com/$username") },
|
||||
indication = rememberRipple(bounded = false, radius = 90.dp),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
.widthIn(min = 100.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.size(71.dp)
|
||||
.clip(CircleShape),
|
||||
model = "https://github.com/$username.png",
|
||||
contentDescription = username
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 18.sp
|
||||
)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = roles,
|
||||
style = MaterialTheme.typography.titleSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.screens.home
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.*
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.domain.manager.PreferencesManager
|
||||
|
|
@ -19,11 +19,11 @@ import com.aliucord.manager.util.getPackageVersion
|
|||
import com.aliucord.manager.util.showToast
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class HomeViewModel(
|
||||
class HomeModel(
|
||||
private val application: Application,
|
||||
private val github: GithubRepository,
|
||||
val preferences: PreferencesManager
|
||||
) : ViewModel() {
|
||||
val preferences: PreferencesManager,
|
||||
) : ScreenModel {
|
||||
var supportedVersion by mutableStateOf<DiscordVersion>(DiscordVersion.None)
|
||||
private set
|
||||
|
||||
|
|
@ -54,10 +54,10 @@ class HomeViewModel(
|
|||
state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
|
||||
}
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
}.flow.cachedIn(screenModelScope)
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
_fetchInstalledVersion()
|
||||
_fetchSupportedVersion()
|
||||
}
|
||||
|
|
@ -110,13 +110,13 @@ class HomeViewModel(
|
|||
}
|
||||
|
||||
fun fetchInstalledVersion() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
_fetchInstalledVersion()
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchSupportedVersion() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
_fetchSupportedVersion()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
|
||||
package com.aliucord.manager.ui.screens.home
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.dialogs.InstallerDialog
|
||||
import com.aliucord.manager.ui.components.home.CommitList
|
||||
import com.aliucord.manager.ui.components.home.InfoCard
|
||||
import com.aliucord.manager.ui.screens.about.AboutScreen
|
||||
import com.aliucord.manager.ui.screens.install.InstallScreen
|
||||
|
||||
class HomeScreen : Screen {
|
||||
override val key = "Home"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = getScreenModel<HomeModel>()
|
||||
|
||||
var showInstallerDialog by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(model.preferences.packageName) {
|
||||
model.fetchInstalledVersion()
|
||||
}
|
||||
|
||||
if (showInstallerDialog) {
|
||||
InstallerDialog(
|
||||
onDismiss = { showInstallerDialog = false },
|
||||
onConfirm = { data ->
|
||||
showInstallerDialog = false
|
||||
navigator.push(InstallScreen(data))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: add a way to open plugins and settings
|
||||
|
||||
Scaffold(
|
||||
topBar = { HomeAppBar() },
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
InfoCard(
|
||||
packageName = model.preferences.packageName,
|
||||
supportedVersion = model.supportedVersion,
|
||||
currentVersion = model.installedVersion,
|
||||
onDownloadClick = { showInstallerDialog = true },
|
||||
onLaunchClick = model::launchAliucord,
|
||||
onUninstallClick = model::uninstallAliucord
|
||||
)
|
||||
|
||||
CommitList(
|
||||
commits = model.commits,
|
||||
onRetry = { model.fetchSupportedVersion() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomeAppBar() {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.navigation_home)) },
|
||||
actions = {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
uriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}")
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_discord),
|
||||
contentDescription = stringResource(R.string.support_server)
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = { navigator.push(AboutScreen()) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = stringResource(R.string.navigation_about)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.screens.install
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
|
|
@ -6,8 +6,8 @@ import android.os.Build
|
|||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.domain.manager.DownloadManager
|
||||
|
|
@ -16,9 +16,8 @@ import com.aliucord.manager.domain.repository.AliucordMavenRepository
|
|||
import com.aliucord.manager.domain.repository.GithubRepository
|
||||
import com.aliucord.manager.installer.util.*
|
||||
import com.aliucord.manager.network.utils.getOrThrow
|
||||
import com.aliucord.manager.ui.component.installer.InstallStatus
|
||||
import com.aliucord.manager.ui.component.installer.InstallStepData
|
||||
import com.aliucord.manager.ui.screen.InstallData
|
||||
import com.aliucord.manager.ui.components.installer.InstallStatus
|
||||
import com.aliucord.manager.ui.components.installer.InstallStepData
|
||||
import com.aliucord.manager.util.*
|
||||
import com.github.diamondminer88.zip.*
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -27,14 +26,14 @@ import java.util.Date
|
|||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
class InstallViewModel(
|
||||
class InstallModel(
|
||||
private val application: Application,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val preferences: PreferencesManager,
|
||||
private val githubRepository: GithubRepository,
|
||||
private val aliucordMaven: AliucordMavenRepository,
|
||||
private val installData: InstallData
|
||||
) : ViewModel() {
|
||||
private val installData: InstallData,
|
||||
) : ScreenModel {
|
||||
private val externalCacheDir = application.externalCacheDir!!
|
||||
private val installationRunning = AtomicBoolean(false)
|
||||
|
||||
|
|
@ -86,7 +85,7 @@ class InstallViewModel(
|
|||
application.showToast(R.string.action_cleared_cache)
|
||||
}
|
||||
|
||||
private val installJob = viewModelScope.launch(Dispatchers.Main) {
|
||||
private val installJob = screenModelScope.launch(Dispatchers.Main) {
|
||||
if (installationRunning.getAndSet(true)) {
|
||||
return@launch
|
||||
}
|
||||
|
|
@ -137,7 +136,7 @@ class InstallViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
override fun onDispose() {
|
||||
if (installationRunning.getAndSet(false)) {
|
||||
installJob.cancel("ViewModel cleared")
|
||||
}
|
||||
|
|
@ -375,7 +374,7 @@ class InstallViewModel(
|
|||
|
||||
enum class InstallStepGroup(
|
||||
@StringRes
|
||||
val nameResId: Int
|
||||
val nameResId: Int,
|
||||
) {
|
||||
APK_DL(R.string.install_group_apk_dl),
|
||||
LIB_DL(R.string.install_group_lib_dl),
|
||||
|
|
@ -388,7 +387,7 @@ class InstallViewModel(
|
|||
val group: InstallStepGroup,
|
||||
|
||||
@StringRes
|
||||
val nameResId: Int
|
||||
val nameResId: Int,
|
||||
) {
|
||||
// Kotlin
|
||||
FETCH_KT_VERSION(InstallStepGroup.APK_DL, R.string.install_step_fetch_kt_version),
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.install
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.BackButton
|
||||
import com.aliucord.manager.ui.components.back
|
||||
import com.aliucord.manager.ui.components.dialogs.DownloadMethod
|
||||
import com.aliucord.manager.ui.components.installer.InstallGroup
|
||||
import com.aliucord.manager.ui.components.installer.InstallStatus
|
||||
import com.aliucord.manager.ui.screens.install.InstallModel.InstallStepGroup
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Immutable // this isn't *really* stable, but this never gets modified after being passed to a composable, so...
|
||||
@Parcelize
|
||||
data class InstallData(
|
||||
val downloadMethod: DownloadMethod,
|
||||
var baseApk: String? = null,
|
||||
var splits: List<String>? = null,
|
||||
) : Parcelable
|
||||
|
||||
class InstallScreen(val data: InstallData) : Screen {
|
||||
override val key = "Install"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = getScreenModel<InstallModel>(parameters = { parametersOf(data) })
|
||||
|
||||
var expandedGroup by remember { mutableStateOf<InstallStepGroup?>(null) }
|
||||
|
||||
if (model.returnToHome)
|
||||
navigator.back(null)
|
||||
|
||||
LaunchedEffect(model.currentStep) {
|
||||
expandedGroup = model.currentStep?.group
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.installer)) },
|
||||
navigationIcon = {
|
||||
// TODO: add confirm to exit dialog to button as well as BackHandler
|
||||
BackButton()
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(Modifier.padding(paddingValues)) {
|
||||
val isCurrentlyProcessing by remember {
|
||||
derivedStateOf {
|
||||
model.steps[model.currentStep]?.status == InstallStatus.ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
if (isCurrentlyProcessing) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.padding(bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
for (group in InstallStepGroup.entries) key(group) {
|
||||
InstallGroup(
|
||||
name = stringResource(group.nameResId),
|
||||
isCurrent = expandedGroup == group,
|
||||
onClick = { expandedGroup = group },
|
||||
subSteps = model.getSteps(group).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
if (model.isFinished && model.stacktrace.isEmpty()) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalButton(onClick = model::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.stacktrace.isNotEmpty()) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = model.stacktrace,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
softWrap = false,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp))
|
||||
.padding(10.dp)
|
||||
.horizontalScroll(rememberScrollState())
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalButton(onClick = model::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
OutlinedButton(onClick = model::saveDebugToFile) {
|
||||
Text(stringResource(R.string.installer_save_file))
|
||||
}
|
||||
|
||||
FilledTonalButton(onClick = model::copyDebugToClipboard) {
|
||||
Text(stringResource(R.string.action_copy))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.screens.plugins
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.aliucordDir
|
||||
import com.aliucord.manager.domain.model.Plugin
|
||||
|
|
@ -11,8 +11,7 @@ import kotlinx.coroutines.*
|
|||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class PluginsViewModel : ViewModel() {
|
||||
class PluginsModel : ScreenModel {
|
||||
var showChangelogDialog by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
|
|
@ -35,10 +34,6 @@ class PluginsViewModel : ViewModel() {
|
|||
this.search = search
|
||||
}
|
||||
|
||||
fun clearSearch() {
|
||||
search = ""
|
||||
}
|
||||
|
||||
fun showChangelogDialog(plugin: Plugin) {
|
||||
showChangelogDialog = true
|
||||
selectedPlugin = plugin
|
||||
|
|
@ -65,13 +60,14 @@ class PluginsViewModel : ViewModel() {
|
|||
private val settingsFile = aliucordDir.resolve("settings/Aliucord.json")
|
||||
|
||||
fun setPluginEnabled(plugin: String, enable: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
withContext(Dispatchers.Main) {
|
||||
enabled.clear()
|
||||
enabled.putAll(loadEnabled())
|
||||
enabled[plugin] = enable
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
try {
|
||||
val settings = settingsFile.inputStream()
|
||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||
|
|
@ -91,6 +87,7 @@ class PluginsViewModel : ViewModel() {
|
|||
private suspend fun loadEnabled(): Map<String, Boolean> = withContext(Dispatchers.IO) {
|
||||
if (!settingsFile.exists()) return@withContext emptyMap()
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
try {
|
||||
settingsFile.inputStream()
|
||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||
|
|
@ -105,11 +102,11 @@ class PluginsViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
screenModelScope.launch {
|
||||
enabled.putAll(loadEnabled())
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
val pluginsDir = aliucordDir.resolve("plugins")
|
||||
|
||||
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.plugins
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.plugins.Changelog
|
||||
import com.aliucord.manager.ui.components.plugins.PluginCard
|
||||
|
||||
class PluginsScreen : Screen {
|
||||
override val key = "Plugins"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = getScreenModel<PluginsModel>()
|
||||
|
||||
if (model.showUninstallDialog) {
|
||||
val plugin = model.selectedPlugin!!
|
||||
|
||||
UninstallPluginDialog(
|
||||
pluginName = plugin.manifest.name,
|
||||
onConfirm = { model.uninstallPlugin(plugin) },
|
||||
onDismiss = model::hideUninstallDialog
|
||||
)
|
||||
}
|
||||
|
||||
if (model.showChangelogDialog) {
|
||||
Changelog(
|
||||
plugin = model.selectedPlugin!!,
|
||||
onDismiss = model::hideChangelogDialog
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
PluginSearch(
|
||||
currentFilter = model.search,
|
||||
onFilterChange = model::search,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
if (model.error) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_error),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (model.plugins.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 15.dp, top = 6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
items(
|
||||
// TODO: remember {} this
|
||||
model.plugins.filter { plugin ->
|
||||
plugin.manifest.run {
|
||||
name.contains(model.search, true)
|
||||
|| description.contains(model.search, true)
|
||||
|| authors.any { (name) -> name.contains(model.search, true) }
|
||||
}
|
||||
}
|
||||
) { plugin ->
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
enabled = model.enabled[plugin.manifest.name] ?: true,
|
||||
onClickDelete = { model.showUninstallDialog(plugin) },
|
||||
onClickShowChangelog = { model.showChangelogDialog(plugin) },
|
||||
onSetEnabled = { model.setPluginEnabled(plugin.manifest.name, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_extension_off),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.plugins_none_installed))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PluginSearch(
|
||||
currentFilter: String,
|
||||
onFilterChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
OutlinedTextField(
|
||||
value = currentFilter,
|
||||
onValueChange = onFilterChange,
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
label = { Text(stringResource(R.string.action_search)) },
|
||||
trailingIcon = {
|
||||
val isFilterBlank by remember { derivedStateOf { currentFilter.isEmpty() } }
|
||||
|
||||
if (!isFilterBlank) {
|
||||
IconButton(onClick = { onFilterChange("") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.action_clear)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(R.string.plugins_search)
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions { focusManager.clearFocus() },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UninstallPluginDialog(
|
||||
pluginName: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(R.string.action_uninstall)
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text("${stringResource(R.string.action_uninstall)} $pluginName")
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.plugins_delete_plugin_body))
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = onConfirm,
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.screens.settings
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.domain.manager.PreferencesManager
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import com.aliucord.manager.ui.components.Theme
|
||||
import com.aliucord.manager.util.showToast
|
||||
import com.aliucord.manager.util.throttle
|
||||
|
||||
class SettingsViewModel(
|
||||
class SettingsModel(
|
||||
private val application: Application,
|
||||
val preferences: PreferencesManager
|
||||
) : ViewModel() {
|
||||
private val checkPackageNameThrottled = throttle(50L, viewModelScope) {
|
||||
val preferences: PreferencesManager,
|
||||
) : ScreenModel {
|
||||
private val checkPackageNameThrottled = throttle(50L, screenModelScope) {
|
||||
packageNameError = !PACKAGE_REGEX.matches(preferences.packageName)
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ class SettingsViewModel(
|
|||
checkPackageNameThrottled()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
override fun onDispose() {
|
||||
if (!PACKAGE_REGEX.matches(preferences.packageName)) {
|
||||
preferences.packageName = "com.aliucord"
|
||||
application.showToast(R.string.settings_invalid_package_name)
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.settings
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.Theme
|
||||
import com.aliucord.manager.ui.components.settings.*
|
||||
|
||||
class SettingsScreen : Screen {
|
||||
override val key = "Settings"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = getScreenModel<SettingsModel>()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
) {
|
||||
val preferences = model.preferences
|
||||
|
||||
if (model.showThemeDialog) {
|
||||
ThemeDialog(
|
||||
currentTheme = preferences.theme,
|
||||
onDismissRequest = model::hideThemeDialog,
|
||||
onConfirm = model::setTheme
|
||||
)
|
||||
}
|
||||
|
||||
SettingsHeader(stringResource(R.string.settings_appearance))
|
||||
|
||||
SettingsItem(
|
||||
modifier = Modifier.clickable(onClick = model::showThemeDialog),
|
||||
icon = { Icon(painterResource(R.drawable.ic_brush), null) },
|
||||
text = { Text(stringResource(R.string.settings_theme)) }
|
||||
) {
|
||||
FilledTonalButton(onClick = model::showThemeDialog) {
|
||||
Text(preferences.theme.toDisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_dynamic_color),
|
||||
pref = preferences.dynamicColor,
|
||||
icon = { Icon(painterResource(R.drawable.ic_palette), null) }
|
||||
) {
|
||||
preferences.dynamicColor = it
|
||||
}
|
||||
|
||||
SettingsHeader(stringResource(R.string.settings_advanced))
|
||||
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_app_name),
|
||||
pref = preferences.appName,
|
||||
onPrefChange = model::setAppName
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_replace_icon),
|
||||
secondaryLabel = stringResource(R.string.setting_replace_icon_desc),
|
||||
pref = preferences.replaceIcon,
|
||||
icon = { Icon(painterResource(R.drawable.ic_app_shortcut), null) }
|
||||
) {
|
||||
preferences.replaceIcon = it
|
||||
}
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_keep_patched_apks),
|
||||
secondaryLabel = stringResource(R.string.setting_keep_patched_apks_desc),
|
||||
icon = { Icon(painterResource(R.drawable.ic_delete_forever), null) },
|
||||
pref = preferences.keepPatchedApks,
|
||||
onPrefChange = { preferences.keepPatchedApks = it },
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.settings_developer_options),
|
||||
pref = preferences.devMode,
|
||||
icon = { Icon(painterResource(R.drawable.ic_code), null) }
|
||||
) {
|
||||
preferences.devMode = it
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = preferences.devMode,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically()
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_package_name),
|
||||
pref = preferences.packageName,
|
||||
onPrefChange = model::setPackageName,
|
||||
error = model.packageNameError
|
||||
)
|
||||
|
||||
SettingsTextField(
|
||||
label = stringResource(R.string.setting_target_version),
|
||||
pref = preferences.version,
|
||||
onPrefChange = model::setVersion
|
||||
)
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_debuggable),
|
||||
secondaryLabel = stringResource(R.string.setting_debuggable_desc),
|
||||
pref = preferences.debuggable,
|
||||
icon = { Icon(painterResource(R.drawable.ic_bug), null) },
|
||||
onPrefChange = { preferences.debuggable = it },
|
||||
)
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_hermes_replace_libcpp),
|
||||
secondaryLabel = stringResource(R.string.setting_hermes_replace_libcpp_desc),
|
||||
icon = { Icon(painterResource(R.drawable.ic_copy_file), null) },
|
||||
pref = preferences.hermesReplaceLibCpp,
|
||||
onPrefChange = { preferences.hermesReplaceLibCpp = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(18.dp),
|
||||
shape = ShapeDefaults.Large,
|
||||
onClick = model::clearCacheDir
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.setting_clear_cache),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThemeDialog(
|
||||
currentTheme: Theme,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (Theme) -> Unit,
|
||||
) {
|
||||
var selectedTheme by rememberSaveable { mutableStateOf(currentTheme) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_brush),
|
||||
contentDescription = stringResource(R.string.settings_theme)
|
||||
)
|
||||
},
|
||||
title = { Text(stringResource(R.string.settings_theme)) },
|
||||
text = {
|
||||
Column {
|
||||
Theme.entries.forEach { theme ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { selectedTheme = theme }
|
||||
.padding(start = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = theme.toDisplayName(),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
RadioButton(
|
||||
selected = theme == selectedTheme,
|
||||
onClick = { selectedTheme = theme }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
onConfirm(selectedTheme)
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.action_apply))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ inline fun <T> Iterable<T>.joinToAnnotatedString(
|
|||
is Char -> append(it)
|
||||
else -> append(it.toString())
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
prefix(this@Builder)
|
||||
for ((count, element) in this@joinToAnnotatedString.withIndex()) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.dialog
|
||||
package com.aliucord.manager.ui.widgets.updater
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -8,12 +8,11 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.viewmodel.UpdaterViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun UpdaterDialog(
|
||||
viewModel: UpdaterViewModel = getViewModel()
|
||||
viewModel: UpdaterViewModel = getViewModel(),
|
||||
) {
|
||||
if (!viewModel.showDialog) return
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
package com.aliucord.manager.ui.widgets.updater
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.*
|
|||
inline fun throttle(
|
||||
skipMs: Long,
|
||||
coroutineScope: CoroutineScope,
|
||||
crossinline destinationFunction: suspend () -> Unit
|
||||
crossinline destinationFunction: suspend () -> Unit,
|
||||
): () -> Unit {
|
||||
var throttleJob: Job? = null
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ kotlin = "1.9.22"
|
|||
kotlinx-immutable = "0.3.7"
|
||||
kotlinx-serialization = "1.5.0"
|
||||
ktor = "2.3.7"
|
||||
navigation-reimagined = "1.3.0" # TODO: migrate to voyager
|
||||
voyager = "1.0.0"
|
||||
zip = "2.1.0"
|
||||
|
||||
[libraries]
|
||||
|
|
@ -58,12 +58,16 @@ ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
|||
ktor-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
|
||||
# Voyager
|
||||
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
|
||||
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
|
||||
voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
|
||||
|
||||
# Other
|
||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||
axml = { module = "com.aliucord:axml", version.ref = "axml" }
|
||||
bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" }
|
||||
coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
||||
navigationReimagined = { module = "dev.olshevski.navigation:reimagined", version.ref = "navigation-reimagined" }
|
||||
zip = { module = "io.github.diamondminer88:zip-android", version.ref = "zip" } # Only use the "aar" artifact
|
||||
|
||||
[bundles]
|
||||
|
|
@ -95,6 +99,11 @@ ktor = [
|
|||
"ktor-content-negotiation",
|
||||
"ktor-serialization-json",
|
||||
]
|
||||
voyager = [
|
||||
"voyager-koin",
|
||||
"voyager-navigator",
|
||||
"voyager-transitions",
|
||||
]
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue