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 [](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)
|
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"
|
jvmTarget = "11"
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs += listOf(
|
||||||
"-Xcontext-receivers",
|
"-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.compose)
|
||||||
implementation(libs.bundles.koin)
|
implementation(libs.bundles.koin)
|
||||||
implementation(libs.bundles.ktor)
|
implementation(libs.bundles.ktor)
|
||||||
|
implementation(libs.bundles.voyager)
|
||||||
|
|
||||||
implementation(libs.compose.ui.tooling.preview)
|
implementation(libs.compose.ui.tooling.preview)
|
||||||
debugImplementation(libs.compose.ui.tooling)
|
debugImplementation(libs.compose.ui.tooling)
|
||||||
|
|
@ -122,7 +126,6 @@ dependencies {
|
||||||
implementation(libs.axml)
|
implementation(libs.axml)
|
||||||
implementation(libs.bouncycastle)
|
implementation(libs.bouncycastle)
|
||||||
implementation(libs.coil)
|
implementation(libs.coil)
|
||||||
implementation(libs.navigationReimagined)
|
|
||||||
implementation(variantOf(libs.zip) { artifactType("aar") })
|
implementation(variantOf(libs.zip) { artifactType("aar") })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,16 @@ import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
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.domain.manager.PreferencesManager
|
||||||
import com.aliucord.manager.ui.dialog.StoragePermissionsDialog
|
import com.aliucord.manager.ui.components.*
|
||||||
import com.aliucord.manager.ui.dialog.UpdaterDialog
|
import com.aliucord.manager.ui.components.dialogs.StoragePermissionsDialog
|
||||||
import com.aliucord.manager.ui.navigation.*
|
import com.aliucord.manager.ui.screens.home.HomeScreen
|
||||||
import com.aliucord.manager.ui.screen.*
|
import com.aliucord.manager.ui.widgets.updater.UpdaterDialog
|
||||||
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 org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
// TODO: move to a path provider in DI
|
||||||
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
|
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
@ -38,12 +37,6 @@ class MainActivity : ComponentActivity() {
|
||||||
isDarkTheme = preferences.theme == Theme.DARK || preferences.theme == Theme.SYSTEM && isSystemInDarkTheme(),
|
isDarkTheme = preferences.theme == Theme.DARK || preferences.theme == Theme.SYSTEM && isSystemInDarkTheme(),
|
||||||
isDynamicColor = preferences.dynamicColor
|
isDynamicColor = preferences.dynamicColor
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController<AppDestination>(AppDestination.Home)
|
|
||||||
|
|
||||||
BackHandler {
|
|
||||||
navController.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
StoragePermissionsDialog()
|
StoragePermissionsDialog()
|
||||||
|
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
|
|
@ -55,36 +48,15 @@ class MainActivity : ComponentActivity() {
|
||||||
UpdaterDialog()
|
UpdaterDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHost(
|
Navigator(
|
||||||
controller = navController,
|
screen = HomeScreen(),
|
||||||
) {
|
onBackPressed = null,
|
||||||
when (val dest = this.currentHostEntry.destination) {
|
) { navigator ->
|
||||||
is BaseScreenDestination -> BaseScreen(
|
BackHandler {
|
||||||
currentNavItem = dest,
|
navigator.back(this@MainActivity)
|
||||||
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() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FadeTransition(navigator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,16 @@ import com.aliucord.manager.di.*
|
||||||
import com.aliucord.manager.domain.repository.AliucordMavenRepository
|
import com.aliucord.manager.domain.repository.AliucordMavenRepository
|
||||||
import com.aliucord.manager.domain.repository.GithubRepository
|
import com.aliucord.manager.domain.repository.GithubRepository
|
||||||
import com.aliucord.manager.network.service.*
|
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.android.ext.koin.androidContext
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
|
import org.koin.core.module.dsl.factoryOf
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
@ -39,13 +45,13 @@ class ManagerApplication : Application() {
|
||||||
singleOf(::AliucordMavenRepository)
|
singleOf(::AliucordMavenRepository)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ViewModels
|
// UI Models
|
||||||
modules(module {
|
modules(module {
|
||||||
viewModelOf(::HomeViewModel)
|
factoryOf(::HomeModel)
|
||||||
viewModelOf(::PluginsViewModel)
|
factoryOf(::PluginsModel)
|
||||||
viewModelOf(::AboutViewModel)
|
factoryOf(::AboutModel)
|
||||||
viewModelOf(::InstallViewModel)
|
factoryOf(::InstallModel)
|
||||||
viewModelOf(::SettingsViewModel)
|
factoryOf(::SettingsModel)
|
||||||
viewModelOf(::UpdaterViewModel)
|
viewModelOf(::UpdaterViewModel)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import java.io.File
|
||||||
import kotlin.coroutines.*
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
class DownloadManager(
|
class DownloadManager(
|
||||||
private val application: Application
|
private val application: Application,
|
||||||
) {
|
) {
|
||||||
private val downloadManager = application.getSystemService<DownloadManager>()!!
|
private val downloadManager = application.getSystemService<DownloadManager>()!!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.aliucord.manager.domain.manager
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.aliucord.manager.domain.manager.base.BasePreferenceManager
|
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) {
|
class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager(preferences) {
|
||||||
var theme by enumPreference("theme", Theme.SYSTEM)
|
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import androidx.core.content.edit
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
abstract class BasePreferenceManager(
|
abstract class BasePreferenceManager(
|
||||||
private val prefs: SharedPreferences
|
private val prefs: SharedPreferences,
|
||||||
) {
|
) {
|
||||||
protected fun getString(key: String, defaultValue: String) = prefs.getString(key, defaultValue) ?: defaultValue
|
protected fun getString(key: String, defaultValue: String) = prefs.getString(key, defaultValue) ?: defaultValue
|
||||||
private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue)
|
private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue)
|
||||||
|
|
@ -25,7 +25,7 @@ abstract class BasePreferenceManager(
|
||||||
private val key: String,
|
private val key: String,
|
||||||
defaultValue: T,
|
defaultValue: T,
|
||||||
getter: (key: String, defaultValue: T) -> 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")
|
@Suppress("RedundantSetter")
|
||||||
var value by mutableStateOf(getter(key, defaultValue))
|
var value by mutableStateOf(getter(key, defaultValue))
|
||||||
|
|
@ -40,7 +40,7 @@ abstract class BasePreferenceManager(
|
||||||
|
|
||||||
protected fun stringPreference(
|
protected fun stringPreference(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: String
|
defaultValue: String,
|
||||||
) = Preference(
|
) = Preference(
|
||||||
key = key,
|
key = key,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
|
@ -50,7 +50,7 @@ abstract class BasePreferenceManager(
|
||||||
|
|
||||||
protected fun booleanPreference(
|
protected fun booleanPreference(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Boolean
|
defaultValue: Boolean,
|
||||||
) = Preference(
|
) = Preference(
|
||||||
key = key,
|
key = key,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
|
@ -60,7 +60,7 @@ abstract class BasePreferenceManager(
|
||||||
|
|
||||||
protected fun intPreference(
|
protected fun intPreference(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Int
|
defaultValue: Int,
|
||||||
) = Preference(
|
) = Preference(
|
||||||
key = key,
|
key = key,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
|
@ -70,7 +70,7 @@ abstract class BasePreferenceManager(
|
||||||
|
|
||||||
protected fun floatPreference(
|
protected fun floatPreference(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Float
|
defaultValue: Float,
|
||||||
) = Preference(
|
) = Preference(
|
||||||
key = key,
|
key = key,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
|
@ -80,7 +80,7 @@ abstract class BasePreferenceManager(
|
||||||
|
|
||||||
protected inline fun <reified E : Enum<E>> enumPreference(
|
protected inline fun <reified E : Enum<E>> enumPreference(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: E
|
defaultValue: E,
|
||||||
) = Preference(
|
) = Preference(
|
||||||
key = key,
|
key = key,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,12 @@ import com.github.diamondminer88.zip.ZipReader
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
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
|
import java.io.File
|
||||||
|
|
||||||
private val json: Json = GlobalContext.get().get()
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Plugin(val file: File) {
|
data class Plugin(val file: File) : KoinComponent {
|
||||||
val manifest: Manifest
|
val manifest: Manifest
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -25,7 +24,7 @@ data class Plugin(val file: File) {
|
||||||
?: throw Exception("Plugin ${file.nameWithoutExtension} has no manifest.")
|
?: throw Exception("Plugin ${file.nameWithoutExtension} has no manifest.")
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@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
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|
||||||
class AliucordMavenRepository(
|
class AliucordMavenRepository(
|
||||||
private val maven: MavenService
|
private val maven: MavenService,
|
||||||
) {
|
) {
|
||||||
suspend fun getAliuhookVersion(): ApiResponse<String> {
|
suspend fun getAliuhookVersion(): ApiResponse<String> {
|
||||||
return maven.getArtifactMetadata(BASE_URL, ALIUHOOK).transform {
|
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
|
import com.aliucord.manager.network.utils.transform
|
||||||
|
|
||||||
class GithubRepository(
|
class GithubRepository(
|
||||||
private val service: AliucordGithubService
|
private val service: AliucordGithubService,
|
||||||
) {
|
) {
|
||||||
suspend fun getCommits(page: Int = 0) = service.getCommits(page)
|
suspend fun getCommits(page: Int = 0) = service.getCommits(page)
|
||||||
suspend fun getContributors() = service.getContributors()
|
suspend fun getContributors() = service.getContributors()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ object ManifestPatcher {
|
||||||
manifestBytes: ByteArray,
|
manifestBytes: ByteArray,
|
||||||
packageName: String,
|
packageName: String,
|
||||||
appName: String,
|
appName: String,
|
||||||
debuggable: Boolean
|
debuggable: Boolean,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
val reader = AxmlReader(manifestBytes)
|
val reader = AxmlReader(manifestBytes)
|
||||||
val writer = AxmlWriter()
|
val writer = AxmlWriter()
|
||||||
|
|
@ -142,7 +142,7 @@ object ManifestPatcher {
|
||||||
|
|
||||||
fun renamePackage(
|
fun renamePackage(
|
||||||
manifestBytes: ByteArray,
|
manifestBytes: ByteArray,
|
||||||
packageName: String
|
packageName: String,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
val reader = AxmlReader(manifestBytes)
|
val reader = AxmlReader(manifestBytes)
|
||||||
val writer = AxmlWriter()
|
val writer = AxmlWriter()
|
||||||
|
|
@ -160,7 +160,7 @@ object ManifestPatcher {
|
||||||
|
|
||||||
private open class ReplaceAttrsVisitor(
|
private open class ReplaceAttrsVisitor(
|
||||||
nv: NodeVisitor,
|
nv: NodeVisitor,
|
||||||
private val attrs: Map<String, Any>
|
private val attrs: Map<String, Any>,
|
||||||
) : NodeVisitor(nv) {
|
) : NodeVisitor(nv) {
|
||||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||||
val replace = attrs.containsKey(name)
|
val replace = attrs.containsKey(name)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ data class Commit(
|
||||||
val htmlUrl: String,
|
val htmlUrl: String,
|
||||||
val sha: String,
|
val sha: String,
|
||||||
val commit: Commit,
|
val commit: Commit,
|
||||||
val author: Author?
|
val author: Author?,
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Author(@SerialName("login") val name: String)
|
data class Author(@SerialName("login") val name: String)
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ data class GithubRelease(
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val assets: List<GithubReleaseAssets>,
|
val assets: List<GithubReleaseAssets>,
|
||||||
@SerialName("tag_name")
|
@SerialName("tag_name")
|
||||||
val tagName: String
|
val tagName: String,
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GithubReleaseAssets(
|
data class GithubReleaseAssets(
|
||||||
val name: String,
|
val name: String,
|
||||||
@SerialName("browser_download_url")
|
@SerialName("browser_download_url")
|
||||||
val browserDownloadUrl: String
|
val browserDownloadUrl: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ import kotlinx.serialization.Serializable
|
||||||
data class GithubUser(
|
data class GithubUser(
|
||||||
@SerialName("login")
|
@SerialName("login")
|
||||||
val name: String,
|
val name: String,
|
||||||
val contributions: Int
|
val contributions: Int,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ import kotlinx.serialization.Serializable
|
||||||
data class Version(
|
data class Version(
|
||||||
val versionCode: String,
|
val versionCode: String,
|
||||||
val versionName: String,
|
val versionName: String,
|
||||||
val aliucordHash: String
|
val aliucordHash: String,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class AliucordGithubService(
|
class AliucordGithubService(
|
||||||
val github: GithubService,
|
val github: GithubService,
|
||||||
val http: HttpService
|
val http: HttpService,
|
||||||
) {
|
) {
|
||||||
suspend fun getDataJson(): ApiResponse<Version> {
|
suspend fun getDataJson(): ApiResponse<Version> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class ApiFailure(error: Throwable, body: String?) : Error(body, error)
|
||||||
inline fun <T, R> ApiResponse<T>.fold(
|
inline fun <T, R> ApiResponse<T>.fold(
|
||||||
success: (T) -> R,
|
success: (T) -> R,
|
||||||
error: (ApiError) -> R,
|
error: (ApiError) -> R,
|
||||||
failure: (ApiFailure) -> R
|
failure: (ApiFailure) -> R,
|
||||||
): R {
|
): R {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ApiResponse.Success -> success(this.data)
|
is ApiResponse.Success -> success(this.data)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ data class SemVer(
|
||||||
val major: Int,
|
val major: Int,
|
||||||
val minor: Int,
|
val minor: Int,
|
||||||
val patch: Int,
|
val patch: Int,
|
||||||
private val vPrefix: Boolean = false
|
private val vPrefix: Boolean = false,
|
||||||
) : Comparable<SemVer> {
|
) : Comparable<SemVer> {
|
||||||
override fun compareTo(other: SemVer): Int {
|
override fun compareTo(other: SemVer): Int {
|
||||||
val pairs = arrayOf(
|
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
|
* 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.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.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.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -22,41 +21,15 @@ import coil.compose.AsyncImage
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.network.dto.GithubUser
|
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
|
@Composable
|
||||||
fun ContributorEntry(user: GithubUser) {
|
fun ContributorCommitsItem(
|
||||||
|
user: GithubUser,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("https://github.com/${user.name}")
|
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.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the Open Software License version 3.0
|
* 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 android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
|
@ -19,7 +19,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
fun ManagerTheme(
|
fun ManagerTheme(
|
||||||
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
isDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
isDynamicColor: Boolean = true,
|
isDynamicColor: Boolean = true,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
|
|
@ -3,18 +3,18 @@
|
||||||
* Licensed under the Open Software License version 3.0
|
* 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.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
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 }
|
enum class DownloadMethod { DOWNLOAD }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InstallerDialog(
|
fun InstallerDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirm: (InstallData) -> Unit
|
onConfirm: (InstallData) -> Unit,
|
||||||
) {
|
) {
|
||||||
val downloadMethod by rememberSaveable { mutableStateOf(DownloadMethod.DOWNLOAD) }
|
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.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -7,6 +7,7 @@ import android.os.Environment
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -26,6 +27,7 @@ fun StoragePermissionsDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
private fun ManageStorageDialog() {
|
private fun ManageStorageDialog() {
|
||||||
var manageStorageGranted by remember { mutableStateOf(Environment.isExternalStorageManager()) }
|
var manageStorageGranted by remember { mutableStateOf(Environment.isExternalStorageManager()) }
|
||||||
|
|
||||||
|
|
@ -59,8 +61,8 @@ private fun ManageStorageDialog() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
private fun ExternalStorageDialog() {
|
private fun ExternalStorageDialog() {
|
||||||
val writeStorageState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -22,7 +22,7 @@ import androidx.paging.compose.items
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.network.dto.Commit
|
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 com.aliucord.manager.ui.util.annotatingStringResource
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
@Composable
|
@Composable
|
||||||
fun CommitList(
|
fun CommitList(
|
||||||
commits: Flow<PagingData<Commit>>,
|
commits: Flow<PagingData<Commit>>,
|
||||||
onRetry: () -> Unit
|
onRetry: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
|
@ -113,7 +113,7 @@ fun CommitList(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommitItem(
|
fun CommitItem(
|
||||||
commit: Commit
|
commit: Commit,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
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.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
|
@ -23,7 +23,7 @@ fun InfoCard(
|
||||||
currentVersion: DiscordVersion,
|
currentVersion: DiscordVersion,
|
||||||
onDownloadClick: () -> Unit,
|
onDownloadClick: () -> Unit,
|
||||||
onUninstallClick: () -> Unit,
|
onUninstallClick: () -> Unit,
|
||||||
onLaunchClick: () -> Unit
|
onLaunchClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
Column(
|
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.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -35,7 +35,7 @@ class InstallStepData(
|
||||||
val nameResId: Int,
|
val nameResId: Int,
|
||||||
status: InstallStatus,
|
status: InstallStatus,
|
||||||
duration: Float = 0f,
|
duration: Float = 0f,
|
||||||
cached: Boolean = false
|
cached: Boolean = false,
|
||||||
) {
|
) {
|
||||||
var status by mutableStateOf(status)
|
var status by mutableStateOf(status)
|
||||||
var duration by mutableStateOf(duration)
|
var duration by mutableStateOf(duration)
|
||||||
|
|
@ -47,7 +47,7 @@ fun InstallGroup(
|
||||||
name: String,
|
name: String,
|
||||||
isCurrent: Boolean,
|
isCurrent: Boolean,
|
||||||
subSteps: ImmutableList<InstallStepData>,
|
subSteps: ImmutableList<InstallStepData>,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val status = when {
|
val status = when {
|
||||||
subSteps.all { it.status == InstallStatus.QUEUED } ->
|
subSteps.all { it.status == InstallStatus.QUEUED } ->
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the Open Software License version 3.0
|
* 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.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
|
@ -64,7 +64,7 @@ private fun AnnotatedString.Builder.hyperlink(content: String) {
|
||||||
@Composable
|
@Composable
|
||||||
fun Changelog(
|
fun Changelog(
|
||||||
plugin: Plugin,
|
plugin: Plugin,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
|
|
@ -164,7 +164,7 @@ fun Changelog(
|
||||||
@Composable
|
@Composable
|
||||||
private fun LinkText(
|
private fun LinkText(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
annotatedString: AnnotatedString
|
annotatedString: AnnotatedString,
|
||||||
) {
|
) {
|
||||||
val urlHandler = LocalUriHandler.current
|
val urlHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the Open Software License version 3.0
|
* 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.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsHeader(
|
fun SettingsHeader(
|
||||||
text: String
|
text: String,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = 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.foundation.layout.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.foundation.clickable
|
||||||
import androidx.compose.material3.Switch
|
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.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsTextField(
|
fun SettingsTextField(
|
||||||
label: String,
|
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.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.domain.repository.GithubRepository
|
import com.aliucord.manager.domain.repository.GithubRepository
|
||||||
import com.aliucord.manager.network.dto.GithubUser
|
import com.aliucord.manager.network.dto.GithubUser
|
||||||
import com.aliucord.manager.network.utils.fold
|
import com.aliucord.manager.network.utils.fold
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AboutViewModel(
|
class AboutModel(
|
||||||
private val githubRepository: GithubRepository
|
private val githubRepository: GithubRepository,
|
||||||
) : ViewModel() {
|
) : ScreenModel {
|
||||||
val contributors = mutableStateListOf<GithubUser>()
|
val contributors = mutableStateListOf<GithubUser>()
|
||||||
var fetchError by mutableStateOf(false)
|
var fetchError by mutableStateOf(false)
|
||||||
|
|
||||||
|
|
@ -19,13 +19,14 @@ class AboutViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchContributors() {
|
fun fetchContributors() {
|
||||||
viewModelScope.launch {
|
screenModelScope.launch {
|
||||||
githubRepository.getContributors().fold(
|
githubRepository.getContributors().fold(
|
||||||
success = {
|
success = {
|
||||||
|
contributors.clear()
|
||||||
contributors.addAll(it)
|
contributors.addAll(it)
|
||||||
fetchError = false
|
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.app.Application
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.paging.*
|
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.BuildConfig
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.domain.manager.PreferencesManager
|
import com.aliucord.manager.domain.manager.PreferencesManager
|
||||||
|
|
@ -19,11 +19,11 @@ import com.aliucord.manager.util.getPackageVersion
|
||||||
import com.aliucord.manager.util.showToast
|
import com.aliucord.manager.util.showToast
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
class HomeViewModel(
|
class HomeModel(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val github: GithubRepository,
|
private val github: GithubRepository,
|
||||||
val preferences: PreferencesManager
|
val preferences: PreferencesManager,
|
||||||
) : ViewModel() {
|
) : ScreenModel {
|
||||||
var supportedVersion by mutableStateOf<DiscordVersion>(DiscordVersion.None)
|
var supportedVersion by mutableStateOf<DiscordVersion>(DiscordVersion.None)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -54,10 +54,10 @@ class HomeViewModel(
|
||||||
state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
|
state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.flow.cachedIn(viewModelScope)
|
}.flow.cachedIn(screenModelScope)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
_fetchInstalledVersion()
|
_fetchInstalledVersion()
|
||||||
_fetchSupportedVersion()
|
_fetchSupportedVersion()
|
||||||
}
|
}
|
||||||
|
|
@ -110,13 +110,13 @@ class HomeViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchInstalledVersion() {
|
fun fetchInstalledVersion() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
_fetchInstalledVersion()
|
_fetchInstalledVersion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchSupportedVersion() {
|
fun fetchSupportedVersion() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
_fetchSupportedVersion()
|
_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.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
|
@ -6,8 +6,8 @@ import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.BuildConfig
|
import com.aliucord.manager.BuildConfig
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.domain.manager.DownloadManager
|
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.domain.repository.GithubRepository
|
||||||
import com.aliucord.manager.installer.util.*
|
import com.aliucord.manager.installer.util.*
|
||||||
import com.aliucord.manager.network.utils.getOrThrow
|
import com.aliucord.manager.network.utils.getOrThrow
|
||||||
import com.aliucord.manager.ui.component.installer.InstallStatus
|
import com.aliucord.manager.ui.components.installer.InstallStatus
|
||||||
import com.aliucord.manager.ui.component.installer.InstallStepData
|
import com.aliucord.manager.ui.components.installer.InstallStepData
|
||||||
import com.aliucord.manager.ui.screen.InstallData
|
|
||||||
import com.aliucord.manager.util.*
|
import com.aliucord.manager.util.*
|
||||||
import com.github.diamondminer88.zip.*
|
import com.github.diamondminer88.zip.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
@ -27,14 +26,14 @@ import java.util.Date
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.time.measureTimedValue
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
class InstallViewModel(
|
class InstallModel(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val downloadManager: DownloadManager,
|
private val downloadManager: DownloadManager,
|
||||||
private val preferences: PreferencesManager,
|
private val preferences: PreferencesManager,
|
||||||
private val githubRepository: GithubRepository,
|
private val githubRepository: GithubRepository,
|
||||||
private val aliucordMaven: AliucordMavenRepository,
|
private val aliucordMaven: AliucordMavenRepository,
|
||||||
private val installData: InstallData
|
private val installData: InstallData,
|
||||||
) : ViewModel() {
|
) : ScreenModel {
|
||||||
private val externalCacheDir = application.externalCacheDir!!
|
private val externalCacheDir = application.externalCacheDir!!
|
||||||
private val installationRunning = AtomicBoolean(false)
|
private val installationRunning = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
@ -86,7 +85,7 @@ class InstallViewModel(
|
||||||
application.showToast(R.string.action_cleared_cache)
|
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)) {
|
if (installationRunning.getAndSet(true)) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +136,7 @@ class InstallViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onDispose() {
|
||||||
if (installationRunning.getAndSet(false)) {
|
if (installationRunning.getAndSet(false)) {
|
||||||
installJob.cancel("ViewModel cleared")
|
installJob.cancel("ViewModel cleared")
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +374,7 @@ class InstallViewModel(
|
||||||
|
|
||||||
enum class InstallStepGroup(
|
enum class InstallStepGroup(
|
||||||
@StringRes
|
@StringRes
|
||||||
val nameResId: Int
|
val nameResId: Int,
|
||||||
) {
|
) {
|
||||||
APK_DL(R.string.install_group_apk_dl),
|
APK_DL(R.string.install_group_apk_dl),
|
||||||
LIB_DL(R.string.install_group_lib_dl),
|
LIB_DL(R.string.install_group_lib_dl),
|
||||||
|
|
@ -388,7 +387,7 @@ class InstallViewModel(
|
||||||
val group: InstallStepGroup,
|
val group: InstallStepGroup,
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
val nameResId: Int
|
val nameResId: Int,
|
||||||
) {
|
) {
|
||||||
// Kotlin
|
// Kotlin
|
||||||
FETCH_KT_VERSION(InstallStepGroup.APK_DL, R.string.install_step_fetch_kt_version),
|
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 android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.BuildConfig
|
import com.aliucord.manager.BuildConfig
|
||||||
import com.aliucord.manager.aliucordDir
|
import com.aliucord.manager.aliucordDir
|
||||||
import com.aliucord.manager.domain.model.Plugin
|
import com.aliucord.manager.domain.model.Plugin
|
||||||
|
|
@ -11,8 +11,7 @@ import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
class PluginsModel : ScreenModel {
|
||||||
class PluginsViewModel : ViewModel() {
|
|
||||||
var showChangelogDialog by mutableStateOf(false)
|
var showChangelogDialog by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -35,10 +34,6 @@ class PluginsViewModel : ViewModel() {
|
||||||
this.search = search
|
this.search = search
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearSearch() {
|
|
||||||
search = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showChangelogDialog(plugin: Plugin) {
|
fun showChangelogDialog(plugin: Plugin) {
|
||||||
showChangelogDialog = true
|
showChangelogDialog = true
|
||||||
selectedPlugin = plugin
|
selectedPlugin = plugin
|
||||||
|
|
@ -65,13 +60,14 @@ class PluginsViewModel : ViewModel() {
|
||||||
private val settingsFile = aliucordDir.resolve("settings/Aliucord.json")
|
private val settingsFile = aliucordDir.resolve("settings/Aliucord.json")
|
||||||
|
|
||||||
fun setPluginEnabled(plugin: String, enable: Boolean) {
|
fun setPluginEnabled(plugin: String, enable: Boolean) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
enabled.clear()
|
enabled.clear()
|
||||||
enabled.putAll(loadEnabled())
|
enabled.putAll(loadEnabled())
|
||||||
enabled[plugin] = enable
|
enabled[plugin] = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
try {
|
try {
|
||||||
val settings = settingsFile.inputStream()
|
val settings = settingsFile.inputStream()
|
||||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||||
|
|
@ -91,6 +87,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
private suspend fun loadEnabled(): Map<String, Boolean> = withContext(Dispatchers.IO) {
|
private suspend fun loadEnabled(): Map<String, Boolean> = withContext(Dispatchers.IO) {
|
||||||
if (!settingsFile.exists()) return@withContext emptyMap()
|
if (!settingsFile.exists()) return@withContext emptyMap()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
try {
|
try {
|
||||||
settingsFile.inputStream()
|
settingsFile.inputStream()
|
||||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||||
|
|
@ -105,11 +102,11 @@ class PluginsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
screenModelScope.launch {
|
||||||
enabled.putAll(loadEnabled())
|
enabled.putAll(loadEnabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
val pluginsDir = aliucordDir.resolve("plugins")
|
val pluginsDir = aliucordDir.resolve("plugins")
|
||||||
|
|
||||||
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
|
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 android.app.Application
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.domain.manager.PreferencesManager
|
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.showToast
|
||||||
import com.aliucord.manager.util.throttle
|
import com.aliucord.manager.util.throttle
|
||||||
|
|
||||||
class SettingsViewModel(
|
class SettingsModel(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
val preferences: PreferencesManager
|
val preferences: PreferencesManager,
|
||||||
) : ViewModel() {
|
) : ScreenModel {
|
||||||
private val checkPackageNameThrottled = throttle(50L, viewModelScope) {
|
private val checkPackageNameThrottled = throttle(50L, screenModelScope) {
|
||||||
packageNameError = !PACKAGE_REGEX.matches(preferences.packageName)
|
packageNameError = !PACKAGE_REGEX.matches(preferences.packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ class SettingsViewModel(
|
||||||
checkPackageNameThrottled()
|
checkPackageNameThrottled()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onDispose() {
|
||||||
if (!PACKAGE_REGEX.matches(preferences.packageName)) {
|
if (!PACKAGE_REGEX.matches(preferences.packageName)) {
|
||||||
preferences.packageName = "com.aliucord"
|
preferences.packageName = "com.aliucord"
|
||||||
application.showToast(R.string.settings_invalid_package_name)
|
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)
|
is Char -> append(it)
|
||||||
else -> append(it.toString())
|
else -> append(it.toString())
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
prefix(this@Builder)
|
prefix(this@Builder)
|
||||||
for ((count, element) in this@joinToAnnotatedString.withIndex()) {
|
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.foundation.layout.size
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -8,12 +8,11 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.ui.viewmodel.UpdaterViewModel
|
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdaterDialog(
|
fun UpdaterDialog(
|
||||||
viewModel: UpdaterViewModel = getViewModel()
|
viewModel: UpdaterViewModel = getViewModel(),
|
||||||
) {
|
) {
|
||||||
if (!viewModel.showDialog) return
|
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 android.app.Application
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.*
|
||||||
inline fun throttle(
|
inline fun throttle(
|
||||||
skipMs: Long,
|
skipMs: Long,
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
crossinline destinationFunction: suspend () -> Unit
|
crossinline destinationFunction: suspend () -> Unit,
|
||||||
): () -> Unit {
|
): () -> Unit {
|
||||||
var throttleJob: Job? = null
|
var throttleJob: Job? = null
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ kotlin = "1.9.22"
|
||||||
kotlinx-immutable = "0.3.7"
|
kotlinx-immutable = "0.3.7"
|
||||||
kotlinx-serialization = "1.5.0"
|
kotlinx-serialization = "1.5.0"
|
||||||
ktor = "2.3.7"
|
ktor = "2.3.7"
|
||||||
navigation-reimagined = "1.3.0" # TODO: migrate to voyager
|
voyager = "1.0.0"
|
||||||
zip = "2.1.0"
|
zip = "2.1.0"
|
||||||
|
|
||||||
[libraries]
|
[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-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" }
|
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
|
# Other
|
||||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||||
axml = { module = "com.aliucord:axml", version.ref = "axml" }
|
axml = { module = "com.aliucord:axml", version.ref = "axml" }
|
||||||
bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" }
|
bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" }
|
||||||
coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
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
|
zip = { module = "io.github.diamondminer88:zip-android", version.ref = "zip" } # Only use the "aar" artifact
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
|
@ -95,6 +99,11 @@ ktor = [
|
||||||
"ktor-content-negotiation",
|
"ktor-content-negotiation",
|
||||||
"ktor-serialization-json",
|
"ktor-serialization-json",
|
||||||
]
|
]
|
||||||
|
voyager = [
|
||||||
|
"voyager-koin",
|
||||||
|
"voyager-navigator",
|
||||||
|
"voyager-transitions",
|
||||||
|
]
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue