From 5339459ac8d3eea35b45cbfac8694d0bcfb8e821 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:01:08 -0800 Subject: [PATCH] feat: move to voyager, clean up, remove NavBar --- README.md | 4 +- app/build.gradle.kts | 7 +- .../com/aliucord/manager/MainActivity.kt | 58 ++--- .../aliucord/manager/ManagerApplication.kt | 20 +- .../manager/domain/manager/DownloadManager.kt | 2 +- .../domain/manager/PreferencesManager.kt | 2 +- .../manager/base/BasePreferencesManager.kt | 14 +- .../aliucord/manager/domain/model/Plugin.kt | 9 +- .../repository/AliucordMavenRepository.kt | 2 +- .../domain/repository/GithubRepository.kt | 2 +- .../manager/installer/util/ManifestPatcher.kt | 6 +- .../aliucord/manager/network/dto/Commit.kt | 2 +- .../manager/network/dto/GithubRelease.kt | 4 +- .../manager/network/dto/GithubUser.kt | 2 +- .../aliucord/manager/network/dto/Version.kt | 2 +- .../network/service/AliucordGithubService.kt | 2 +- .../manager/network/utils/ApiResponse.kt | 2 +- .../aliucord/manager/network/utils/SemVer.kt | 2 +- .../manager/ui/components/BackButton.kt | 50 +++++ .../{component => components}/Contributors.kt | 44 +--- .../{component => components}/LoadFailure.kt | 2 +- .../manager/ui/{theme => components}/Theme.kt | 4 +- .../dialogs}/InstallerDialog.kt | 6 +- .../dialogs}/StoragePermissionsDialog.kt | 6 +- .../home/CommitList.kt | 8 +- .../home/InfoCard.kt | 4 +- .../installer/InstallGroup.kt | 6 +- .../plugins/Changelog.kt | 6 +- .../plugins/PluginCard.kt | 2 +- .../settings/SettingsHeader.kt | 4 +- .../settings/SettingsItem.kt | 2 +- .../settings/SettingsSwitch.kt | 2 +- .../settings/SettingsTextField.kt | 6 +- .../manager/ui/navigation/AppDestination.kt | 62 ------ .../manager/ui/navigation/BaseScreen.kt | 82 ------- .../aliucord/manager/ui/screen/AboutScreen.kt | 181 --------------- .../aliucord/manager/ui/screen/HomeScreen.kt | 60 ----- .../manager/ui/screen/InstallScreen.kt | 146 ------------ .../manager/ui/screen/PluginsScreen.kt | 179 --------------- .../manager/ui/screen/SettingsScreen.kt | 206 ----------------- .../about/AboutModel.kt} | 17 +- .../manager/ui/screens/about/AboutScreen.kt | 208 +++++++++++++++++ .../home/HomeModel.kt} | 20 +- .../manager/ui/screens/home/HomeScreen.kt | 113 ++++++++++ .../install/InstallModel.kt} | 25 +-- .../ui/screens/install/InstallScreen.kt | 153 +++++++++++++ .../plugins/PluginsModel.kt} | 21 +- .../ui/screens/plugins/PluginsScreen.kt | 207 +++++++++++++++++ .../settings/SettingsModel.kt} | 18 +- .../ui/screens/settings/SettingsScreen.kt | 210 ++++++++++++++++++ .../com/aliucord/manager/ui/util/Utils.kt | 2 +- .../updater}/UpdaterDialog.kt | 5 +- .../updater}/UpdaterViewModel.kt | 2 +- .../com/aliucord/manager/util/Throttling.kt | 2 +- gradle/libs.versions.toml | 13 +- 55 files changed, 1106 insertions(+), 1120 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/components/BackButton.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/Contributors.kt (62%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/LoadFailure.kt (95%) rename app/src/main/kotlin/com/aliucord/manager/ui/{theme => components}/Theme.kt (95%) rename app/src/main/kotlin/com/aliucord/manager/ui/{dialog => components/dialogs}/InstallerDialog.kt (96%) rename app/src/main/kotlin/com/aliucord/manager/ui/{dialog => components/dialogs}/StoragePermissionsDialog.kt (95%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/home/CommitList.kt (97%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/home/InfoCard.kt (98%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/installer/InstallGroup.kt (98%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/plugins/Changelog.kt (98%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/plugins/PluginCard.kt (99%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/settings/SettingsHeader.kt (86%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/settings/SettingsItem.kt (97%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/settings/SettingsSwitch.kt (94%) rename app/src/main/kotlin/com/aliucord/manager/ui/{component => components}/settings/SettingsTextField.kt (82%) delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/navigation/BaseScreen.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel/AboutViewModel.kt => screens/about/AboutModel.kt} (63%) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel/HomeViewModel.kt => screens/home/HomeModel.kt} (91%) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel/InstallViewModel.kt => screens/install/InstallModel.kt} (96%) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel/PluginsViewModel.kt => screens/plugins/PluginsModel.kt} (89%) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel/SettingsViewModel.kt => screens/settings/SettingsModel.kt} (80%) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt rename app/src/main/kotlin/com/aliucord/manager/ui/{dialog => widgets/updater}/UpdaterDialog.kt (91%) rename app/src/main/kotlin/com/aliucord/manager/ui/{viewmodel => widgets/updater}/UpdaterViewModel.kt (98%) diff --git a/README.md b/README.md index 48e033e..5ee616e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Aliucord Manager [![Crowdin](https://badges.crowdin.net/aliucord-manager/localized.svg)](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) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ddb090a..ed9a6d8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,7 +90,10 @@ android { jvmTarget = "11" freeCompilerArgs += listOf( "-Xcontext-receivers", - "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${reportsDir}", + "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", + "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", +// "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${reportsDir}", ) } @@ -110,6 +113,7 @@ dependencies { implementation(libs.bundles.compose) implementation(libs.bundles.koin) implementation(libs.bundles.ktor) + implementation(libs.bundles.voyager) implementation(libs.compose.ui.tooling.preview) debugImplementation(libs.compose.ui.tooling) @@ -122,7 +126,6 @@ dependencies { implementation(libs.axml) implementation(libs.bouncycastle) implementation(libs.coil) - implementation(libs.navigationReimagined) implementation(variantOf(libs.zip) { artifactType("aar") }) } diff --git a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt index 740715d..aac1d9c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt +++ b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt @@ -12,17 +12,16 @@ import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.compose.foundation.isSystemInDarkTheme import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.transitions.FadeTransition import com.aliucord.manager.domain.manager.PreferencesManager -import com.aliucord.manager.ui.dialog.StoragePermissionsDialog -import com.aliucord.manager.ui.dialog.UpdaterDialog -import com.aliucord.manager.ui.navigation.* -import com.aliucord.manager.ui.screen.* -import com.aliucord.manager.ui.theme.ManagerTheme -import com.aliucord.manager.ui.theme.Theme -import dev.olshevski.navigation.reimagined.* -import kotlinx.collections.immutable.persistentListOf +import com.aliucord.manager.ui.components.* +import com.aliucord.manager.ui.components.dialogs.StoragePermissionsDialog +import com.aliucord.manager.ui.screens.home.HomeScreen +import com.aliucord.manager.ui.widgets.updater.UpdaterDialog import org.koin.android.ext.android.inject +// TODO: move to a path provider in DI val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord") class MainActivity : ComponentActivity() { @@ -38,12 +37,6 @@ class MainActivity : ComponentActivity() { isDarkTheme = preferences.theme == Theme.DARK || preferences.theme == Theme.SYSTEM && isSystemInDarkTheme(), isDynamicColor = preferences.dynamicColor ) { - val navController = rememberNavController(AppDestination.Home) - - BackHandler { - navController.back() - } - StoragePermissionsDialog() @Suppress("KotlinConstantConditions") @@ -55,36 +48,15 @@ class MainActivity : ComponentActivity() { UpdaterDialog() } - NavHost( - controller = navController, - ) { - when (val dest = this.currentHostEntry.destination) { - is BaseScreenDestination -> BaseScreen( - currentNavItem = dest, - bottomNavItems = persistentListOf(AppDestination.Home, AppDestination.Plugins, AppDestination.Settings), - onNavChanged = { navController.replaceLast(it) } - ) { - when (dest) { - is AppDestination.Home -> HomeScreen( - onClickInstall = { data -> - navController.navigate(AppDestination.Install(data)) - } - ) - - is AppDestination.Plugins -> PluginsScreen() - is AppDestination.Settings -> SettingsScreen() - } - } - - is AppDestination.Install -> InstallerScreen( - installData = dest.installData, - onBackClick = { navController.back() } - ) - - is AppDestination.About -> AboutScreen( - onBackClick = { navController.back() } - ) + Navigator( + screen = HomeScreen(), + onBackPressed = null, + ) { navigator -> + BackHandler { + navigator.back(this@MainActivity) } + + FadeTransition(navigator) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt index 25e3ac4..db70858 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt @@ -5,10 +5,16 @@ import com.aliucord.manager.di.* import com.aliucord.manager.domain.repository.AliucordMavenRepository import com.aliucord.manager.domain.repository.GithubRepository import com.aliucord.manager.network.service.* -import com.aliucord.manager.ui.viewmodel.* +import com.aliucord.manager.ui.screens.about.AboutModel +import com.aliucord.manager.ui.screens.home.HomeModel +import com.aliucord.manager.ui.screens.install.InstallModel +import com.aliucord.manager.ui.screens.plugins.PluginsModel +import com.aliucord.manager.ui.screens.settings.SettingsModel +import com.aliucord.manager.ui.widgets.updater.UpdaterViewModel import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.core.context.startKoin +import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -39,13 +45,13 @@ class ManagerApplication : Application() { singleOf(::AliucordMavenRepository) }) - // ViewModels + // UI Models modules(module { - viewModelOf(::HomeViewModel) - viewModelOf(::PluginsViewModel) - viewModelOf(::AboutViewModel) - viewModelOf(::InstallViewModel) - viewModelOf(::SettingsViewModel) + factoryOf(::HomeModel) + factoryOf(::PluginsModel) + factoryOf(::AboutModel) + factoryOf(::InstallModel) + factoryOf(::SettingsModel) viewModelOf(::UpdaterViewModel) }) diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt b/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt index 8cb790d..0cba33b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt @@ -12,7 +12,7 @@ import java.io.File import kotlin.coroutines.* class DownloadManager( - private val application: Application + private val application: Application, ) { private val downloadManager = application.getSystemService()!! diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/manager/PreferencesManager.kt b/app/src/main/kotlin/com/aliucord/manager/domain/manager/PreferencesManager.kt index cf19323..ff2f3f1 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/manager/PreferencesManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/PreferencesManager.kt @@ -2,7 +2,7 @@ package com.aliucord.manager.domain.manager import android.content.SharedPreferences import com.aliucord.manager.domain.manager.base.BasePreferenceManager -import com.aliucord.manager.ui.theme.Theme +import com.aliucord.manager.ui.components.Theme class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager(preferences) { var theme by enumPreference("theme", Theme.SYSTEM) diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/manager/base/BasePreferencesManager.kt b/app/src/main/kotlin/com/aliucord/manager/domain/manager/base/BasePreferencesManager.kt index 84ad1a9..827dc4c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/manager/base/BasePreferencesManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/base/BasePreferencesManager.kt @@ -6,7 +6,7 @@ import androidx.core.content.edit import kotlin.reflect.KProperty abstract class BasePreferenceManager( - private val prefs: SharedPreferences + private val prefs: SharedPreferences, ) { protected fun getString(key: String, defaultValue: String) = prefs.getString(key, defaultValue) ?: defaultValue private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue) @@ -25,7 +25,7 @@ abstract class BasePreferenceManager( private val key: String, defaultValue: T, getter: (key: String, defaultValue: T) -> T, - private val setter: (key: String, newValue: T) -> Unit + private val setter: (key: String, newValue: T) -> Unit, ) { @Suppress("RedundantSetter") var value by mutableStateOf(getter(key, defaultValue)) @@ -40,7 +40,7 @@ abstract class BasePreferenceManager( protected fun stringPreference( key: String, - defaultValue: String + defaultValue: String, ) = Preference( key = key, defaultValue = defaultValue, @@ -50,7 +50,7 @@ abstract class BasePreferenceManager( protected fun booleanPreference( key: String, - defaultValue: Boolean + defaultValue: Boolean, ) = Preference( key = key, defaultValue = defaultValue, @@ -60,7 +60,7 @@ abstract class BasePreferenceManager( protected fun intPreference( key: String, - defaultValue: Int + defaultValue: Int, ) = Preference( key = key, defaultValue = defaultValue, @@ -70,7 +70,7 @@ abstract class BasePreferenceManager( protected fun floatPreference( key: String, - defaultValue: Float + defaultValue: Float, ) = Preference( key = key, defaultValue = defaultValue, @@ -80,7 +80,7 @@ abstract class BasePreferenceManager( protected inline fun > enumPreference( key: String, - defaultValue: E + defaultValue: E, ) = Preference( key = key, defaultValue = defaultValue, diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/model/Plugin.kt b/app/src/main/kotlin/com/aliucord/manager/domain/model/Plugin.kt index 1672162..3161579 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/model/Plugin.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/model/Plugin.kt @@ -10,13 +10,12 @@ import com.github.diamondminer88.zip.ZipReader import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream -import org.koin.core.context.GlobalContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.File -private val json: Json = GlobalContext.get().get() - @Immutable -data class Plugin(val file: File) { +data class Plugin(val file: File) : KoinComponent { val manifest: Manifest init { @@ -25,7 +24,7 @@ data class Plugin(val file: File) { ?: throw Exception("Plugin ${file.nameWithoutExtension} has no manifest.") @OptIn(ExperimentalSerializationApi::class) - this.manifest = json.decodeFromStream(manifest.read().inputStream()) + this.manifest = get().decodeFromStream(manifest.read().inputStream()) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/repository/AliucordMavenRepository.kt b/app/src/main/kotlin/com/aliucord/manager/domain/repository/AliucordMavenRepository.kt index 27a293b..71f84a7 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/repository/AliucordMavenRepository.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/repository/AliucordMavenRepository.kt @@ -5,7 +5,7 @@ import com.aliucord.manager.network.utils.* import io.ktor.http.HttpStatusCode class AliucordMavenRepository( - private val maven: MavenService + private val maven: MavenService, ) { suspend fun getAliuhookVersion(): ApiResponse { return maven.getArtifactMetadata(BASE_URL, ALIUHOOK).transform { diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt b/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt index 9947f2c..5d02ffd 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt @@ -5,7 +5,7 @@ import com.aliucord.manager.network.service.AliucordGithubService import com.aliucord.manager.network.utils.transform class GithubRepository( - private val service: AliucordGithubService + private val service: AliucordGithubService, ) { suspend fun getCommits(page: Int = 0) = service.getCommits(page) suspend fun getContributors() = service.getContributors() diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt index d91fce3..dc0cd49 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt @@ -24,7 +24,7 @@ object ManifestPatcher { manifestBytes: ByteArray, packageName: String, appName: String, - debuggable: Boolean + debuggable: Boolean, ): ByteArray { val reader = AxmlReader(manifestBytes) val writer = AxmlWriter() @@ -142,7 +142,7 @@ object ManifestPatcher { fun renamePackage( manifestBytes: ByteArray, - packageName: String + packageName: String, ): ByteArray { val reader = AxmlReader(manifestBytes) val writer = AxmlWriter() @@ -160,7 +160,7 @@ object ManifestPatcher { private open class ReplaceAttrsVisitor( nv: NodeVisitor, - private val attrs: Map + private val attrs: Map, ) : NodeVisitor(nv) { override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { val replace = attrs.containsKey(name) diff --git a/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt index 600495e..bea542c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt @@ -14,7 +14,7 @@ data class Commit( val htmlUrl: String, val sha: String, val commit: Commit, - val author: Author? + val author: Author?, ) { @Serializable data class Author(@SerialName("login") val name: String) diff --git a/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubRelease.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubRelease.kt index 8767c82..731c5f4 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubRelease.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubRelease.kt @@ -9,12 +9,12 @@ data class GithubRelease( val createdAt: String, val assets: List, @SerialName("tag_name") - val tagName: String + val tagName: String, ) { @Serializable data class GithubReleaseAssets( val name: String, @SerialName("browser_download_url") - val browserDownloadUrl: String + val browserDownloadUrl: String, ) } diff --git a/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt index 331544f..04c09a0 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt @@ -12,5 +12,5 @@ import kotlinx.serialization.Serializable data class GithubUser( @SerialName("login") val name: String, - val contributions: Int + val contributions: Int, ) diff --git a/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt index fdf0b14..f610d32 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt @@ -11,5 +11,5 @@ import kotlinx.serialization.Serializable data class Version( val versionCode: String, val versionName: String, - val aliucordHash: String + val aliucordHash: String, ) diff --git a/app/src/main/kotlin/com/aliucord/manager/network/service/AliucordGithubService.kt b/app/src/main/kotlin/com/aliucord/manager/network/service/AliucordGithubService.kt index 4cd77d0..bedf5b9 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/service/AliucordGithubService.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/service/AliucordGithubService.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext class AliucordGithubService( val github: GithubService, - val http: HttpService + val http: HttpService, ) { suspend fun getDataJson(): ApiResponse { return withContext(Dispatchers.IO) { diff --git a/app/src/main/kotlin/com/aliucord/manager/network/utils/ApiResponse.kt b/app/src/main/kotlin/com/aliucord/manager/network/utils/ApiResponse.kt index 6e301bc..32cffe2 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/utils/ApiResponse.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/utils/ApiResponse.kt @@ -17,7 +17,7 @@ class ApiFailure(error: Throwable, body: String?) : Error(body, error) inline fun ApiResponse.fold( success: (T) -> R, error: (ApiError) -> R, - failure: (ApiFailure) -> R + failure: (ApiFailure) -> R, ): R { return when (this) { is ApiResponse.Success -> success(this.data) diff --git a/app/src/main/kotlin/com/aliucord/manager/network/utils/SemVer.kt b/app/src/main/kotlin/com/aliucord/manager/network/utils/SemVer.kt index 6ea09b6..e7d4f17 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/utils/SemVer.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/utils/SemVer.kt @@ -7,7 +7,7 @@ data class SemVer( val major: Int, val minor: Int, val patch: Int, - private val vPrefix: Boolean = false + private val vPrefix: Boolean = false, ) : Comparable { override fun compareTo(other: SemVer): Int { val pairs = arrayOf( diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/BackButton.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/BackButton.kt new file mode 100644 index 0000000..a378b58 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/BackButton.kt @@ -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()) + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/Contributors.kt similarity index 62% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/Contributors.kt index 2327774..b6fb906 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/Contributors.kt @@ -3,14 +3,13 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.ui.component +package com.aliucord.manager.ui.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,41 +21,15 @@ import coil.compose.AsyncImage import com.aliucord.manager.R import com.aliucord.manager.network.dto.GithubUser -fun LazyListScope.contributors(contributors: List) { - - item { - Text( - text = stringResource(R.string.contributors), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(start = 16.dp, bottom = 16.dp) - ) - } - - if (contributors.isNotEmpty()) { - items(contributors) { user -> - ContributorEntry(user = user) - } - } else { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - contentAlignment = Alignment.Center, - content = { CircularProgressIndicator() } - ) - } - } -} - @Composable -fun ContributorEntry(user: GithubUser) { +fun ContributorCommitsItem( + user: GithubUser, + modifier: Modifier = Modifier, +) { val uriHandler = LocalUriHandler.current Row( - modifier = Modifier + modifier = modifier .clickable { uriHandler.openUri("https://github.com/${user.name}") } @@ -85,6 +58,5 @@ fun ContributorEntry(user: GithubUser) { ) ) } - } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/LoadFailure.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/LoadFailure.kt similarity index 95% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/LoadFailure.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/LoadFailure.kt index 296be5c..1f69c9c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/LoadFailure.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/LoadFailure.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component +package com.aliucord.manager.ui.components import androidx.compose.foundation.layout.* import androidx.compose.material3.* diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/Theme.kt similarity index 95% rename from app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/Theme.kt index 06efa9e..ddc10e1 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/Theme.kt @@ -3,7 +3,7 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.ui.theme +package com.aliucord.manager.ui.components import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme @@ -19,7 +19,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController fun ManagerTheme( isDarkTheme: Boolean = isSystemInDarkTheme(), isDynamicColor: Boolean = true, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val colorScheme = when { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/InstallerDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/InstallerDialog.kt similarity index 96% rename from app/src/main/kotlin/com/aliucord/manager/ui/dialog/InstallerDialog.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/InstallerDialog.kt index 9c1665c..a4fa025 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/InstallerDialog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/InstallerDialog.kt @@ -3,18 +3,18 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.ui.dialog +package com.aliucord.manager.ui.components.dialogs import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import com.aliucord.manager.ui.screen.InstallData +import com.aliucord.manager.ui.screens.install.InstallData enum class DownloadMethod { DOWNLOAD } @Composable fun InstallerDialog( onDismiss: () -> Unit, - onConfirm: (InstallData) -> Unit + onConfirm: (InstallData) -> Unit, ) { val downloadMethod by rememberSaveable { mutableStateOf(DownloadMethod.DOWNLOAD) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/StoragePermissionsDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/StoragePermissionsDialog.kt similarity index 95% rename from app/src/main/kotlin/com/aliucord/manager/ui/dialog/StoragePermissionsDialog.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/StoragePermissionsDialog.kt index ce1c83f..ebb1895 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/StoragePermissionsDialog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/StoragePermissionsDialog.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.dialog +package com.aliucord.manager.ui.components.dialogs import android.Manifest import android.content.Intent @@ -7,6 +7,7 @@ import android.os.Environment import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.res.stringResource @@ -26,6 +27,7 @@ fun StoragePermissionsDialog() { } @Composable +@RequiresApi(Build.VERSION_CODES.R) private fun ManageStorageDialog() { var manageStorageGranted by remember { mutableStateOf(Environment.isExternalStorageManager()) } @@ -59,8 +61,8 @@ private fun ManageStorageDialog() { } } -@OptIn(ExperimentalPermissionsApi::class) @Composable +@OptIn(ExperimentalPermissionsApi::class) private fun ExternalStorageDialog() { val writeStorageState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/home/CommitList.kt similarity index 97% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/home/CommitList.kt index f07832c..a8626fe 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/home/CommitList.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.home +package com.aliucord.manager.ui.components.home import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -22,7 +22,7 @@ import androidx.paging.compose.items import coil.compose.AsyncImage import com.aliucord.manager.R import com.aliucord.manager.network.dto.Commit -import com.aliucord.manager.ui.component.LoadFailure +import com.aliucord.manager.ui.components.LoadFailure import com.aliucord.manager.ui.util.annotatingStringResource import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.Flow @@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.Flow @Composable fun CommitList( commits: Flow>, - onRetry: () -> Unit + onRetry: () -> Unit, ) { ElevatedCard( modifier = Modifier.padding(bottom = 16.dp) @@ -113,7 +113,7 @@ fun CommitList( @Composable fun CommitItem( - commit: Commit + commit: Commit, ) { val uriHandler = LocalUriHandler.current diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/home/InfoCard.kt similarity index 98% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/home/InfoCard.kt index 4043cc9..e420182 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/home/InfoCard.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.home +package com.aliucord.manager.ui.components.home import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons @@ -23,7 +23,7 @@ fun InfoCard( currentVersion: DiscordVersion, onDownloadClick: () -> Unit, onUninstallClick: () -> Unit, - onLaunchClick: () -> Unit + onLaunchClick: () -> Unit, ) { ElevatedCard { Column( diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallGroup.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/installer/InstallGroup.kt similarity index 98% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallGroup.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/installer/InstallGroup.kt index 8c8027e..461a7f1 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallGroup.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/installer/InstallGroup.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.installer +package com.aliucord.manager.ui.components.installer import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background @@ -35,7 +35,7 @@ class InstallStepData( val nameResId: Int, status: InstallStatus, duration: Float = 0f, - cached: Boolean = false + cached: Boolean = false, ) { var status by mutableStateOf(status) var duration by mutableStateOf(duration) @@ -47,7 +47,7 @@ fun InstallGroup( name: String, isCurrent: Boolean, subSteps: ImmutableList, - onClick: () -> Unit + onClick: () -> Unit, ) { val status = when { subSteps.all { it.status == InstallStatus.QUEUED } -> diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt similarity index 98% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt index 8405c5a..d51a8ac 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt @@ -3,7 +3,7 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.ui.component.plugins +package com.aliucord.manager.ui.components.plugins import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -64,7 +64,7 @@ private fun AnnotatedString.Builder.hyperlink(content: String) { @Composable fun Changelog( plugin: Plugin, - onDismiss: () -> Unit + onDismiss: () -> Unit, ) { AlertDialog( onDismissRequest = onDismiss, @@ -164,7 +164,7 @@ fun Changelog( @Composable private fun LinkText( modifier: Modifier = Modifier, - annotatedString: AnnotatedString + annotatedString: AnnotatedString, ) { val urlHandler = LocalUriHandler.current diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/PluginCard.kt similarity index 99% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/PluginCard.kt index 9da39ee..a57839a 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/PluginCard.kt @@ -3,7 +3,7 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.ui.component.plugins +package com.aliucord.manager.ui.components.plugins import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsHeader.kt similarity index 86% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsHeader.kt index 918309c..d9fad56 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsHeader.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.settings +package com.aliucord.manager.ui.components.settings import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme @@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp @Composable fun SettingsHeader( - text: String + text: String, ) { Text( text = text, diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsItem.kt similarity index 97% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsItem.kt index ec56a7f..1a60121 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsItem.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.settings +package com.aliucord.manager.ui.components.settings import androidx.compose.foundation.layout.* import androidx.compose.material3.MaterialTheme diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsSwitch.kt similarity index 94% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsSwitch.kt index ab2b777..9d9f4d3 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsSwitch.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.component.settings +package com.aliucord.manager.ui.components.settings import androidx.compose.foundation.clickable import androidx.compose.material3.Switch diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsTextField.kt similarity index 82% rename from app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsTextField.kt index a10f1d9..d32b0b8 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/SettingsTextField.kt @@ -1,12 +1,12 @@ -package com.aliucord.manager.ui.component.settings +package com.aliucord.manager.ui.components.settings import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsTextField( label: String, diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt deleted file mode 100644 index 938ff4a..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt +++ /dev/null @@ -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.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) - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/BaseScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/BaseScreen.kt deleted file mode 100644 index 3130f91..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/BaseScreen.kt +++ /dev/null @@ -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, - 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() - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt deleted file mode 100644 index bc787c1..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt +++ /dev/null @@ -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) - ) - ) - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt deleted file mode 100644 index 2041abd..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt +++ /dev/null @@ -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() } - ) - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt deleted file mode 100644 index 0716330..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt +++ /dev/null @@ -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? = 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(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)) - } - } - } - } - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt deleted file mode 100644 index 0e23286..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt +++ /dev/null @@ -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)) - } - } - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt deleted file mode 100644 index 8513e36..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt +++ /dev/null @@ -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)) - } - } - ) -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutModel.kt similarity index 63% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutModel.kt index 1c5d784..1d01066 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutModel.kt @@ -1,16 +1,16 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.screens.about import androidx.compose.runtime.* -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.domain.repository.GithubRepository import com.aliucord.manager.network.dto.GithubUser import com.aliucord.manager.network.utils.fold import kotlinx.coroutines.launch -class AboutViewModel( - private val githubRepository: GithubRepository -) : ViewModel() { +class AboutModel( + private val githubRepository: GithubRepository, +) : ScreenModel { val contributors = mutableStateListOf() var fetchError by mutableStateOf(false) @@ -19,13 +19,14 @@ class AboutViewModel( } fun fetchContributors() { - viewModelScope.launch { + screenModelScope.launch { githubRepository.getContributors().fold( success = { + contributors.clear() contributors.addAll(it) fetchError = false }, - fail = { fetchError = true } + fail = { fetchError = true }, ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutScreen.kt new file mode 100644 index 0000000..2d2b1db --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/about/AboutScreen.kt @@ -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() + + 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) + ) + ) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeModel.kt similarity index 91% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeModel.kt index a29ec26..2df6fa6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeModel.kt @@ -1,12 +1,12 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.screens.home import android.app.Application import android.content.pm.PackageManager import android.util.Log import androidx.compose.runtime.* -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import androidx.paging.* +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.BuildConfig import com.aliucord.manager.R import com.aliucord.manager.domain.manager.PreferencesManager @@ -19,11 +19,11 @@ import com.aliucord.manager.util.getPackageVersion import com.aliucord.manager.util.showToast import kotlinx.coroutines.* -class HomeViewModel( +class HomeModel( private val application: Application, private val github: GithubRepository, - val preferences: PreferencesManager -) : ViewModel() { + val preferences: PreferencesManager, +) : ScreenModel { var supportedVersion by mutableStateOf(DiscordVersion.None) private set @@ -54,10 +54,10 @@ class HomeViewModel( state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1) } } - }.flow.cachedIn(viewModelScope) + }.flow.cachedIn(screenModelScope) init { - viewModelScope.launch(Dispatchers.IO) { + screenModelScope.launch(Dispatchers.IO) { _fetchInstalledVersion() _fetchSupportedVersion() } @@ -110,13 +110,13 @@ class HomeViewModel( } fun fetchInstalledVersion() { - viewModelScope.launch(Dispatchers.IO) { + screenModelScope.launch(Dispatchers.IO) { _fetchInstalledVersion() } } fun fetchSupportedVersion() { - viewModelScope.launch(Dispatchers.IO) { + screenModelScope.launch(Dispatchers.IO) { _fetchSupportedVersion() } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt new file mode 100644 index 0000000..8fe39dd --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt @@ -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() + + 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) + ) + } + } + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt similarity index 96% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt index 596f84c..eb9877c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.screens.install import android.annotation.SuppressLint import android.app.Application @@ -6,8 +6,8 @@ import android.os.Build import android.util.Log import androidx.annotation.StringRes import androidx.compose.runtime.* -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.BuildConfig import com.aliucord.manager.R import com.aliucord.manager.domain.manager.DownloadManager @@ -16,9 +16,8 @@ import com.aliucord.manager.domain.repository.AliucordMavenRepository import com.aliucord.manager.domain.repository.GithubRepository import com.aliucord.manager.installer.util.* import com.aliucord.manager.network.utils.getOrThrow -import com.aliucord.manager.ui.component.installer.InstallStatus -import com.aliucord.manager.ui.component.installer.InstallStepData -import com.aliucord.manager.ui.screen.InstallData +import com.aliucord.manager.ui.components.installer.InstallStatus +import com.aliucord.manager.ui.components.installer.InstallStepData import com.aliucord.manager.util.* import com.github.diamondminer88.zip.* import kotlinx.coroutines.* @@ -27,14 +26,14 @@ import java.util.Date import java.util.concurrent.atomic.AtomicBoolean import kotlin.time.measureTimedValue -class InstallViewModel( +class InstallModel( private val application: Application, private val downloadManager: DownloadManager, private val preferences: PreferencesManager, private val githubRepository: GithubRepository, private val aliucordMaven: AliucordMavenRepository, - private val installData: InstallData -) : ViewModel() { + private val installData: InstallData, +) : ScreenModel { private val externalCacheDir = application.externalCacheDir!! private val installationRunning = AtomicBoolean(false) @@ -86,7 +85,7 @@ class InstallViewModel( application.showToast(R.string.action_cleared_cache) } - private val installJob = viewModelScope.launch(Dispatchers.Main) { + private val installJob = screenModelScope.launch(Dispatchers.Main) { if (installationRunning.getAndSet(true)) { return@launch } @@ -137,7 +136,7 @@ class InstallViewModel( } } - override fun onCleared() { + override fun onDispose() { if (installationRunning.getAndSet(false)) { installJob.cancel("ViewModel cleared") } @@ -375,7 +374,7 @@ class InstallViewModel( enum class InstallStepGroup( @StringRes - val nameResId: Int + val nameResId: Int, ) { APK_DL(R.string.install_group_apk_dl), LIB_DL(R.string.install_group_lib_dl), @@ -388,7 +387,7 @@ class InstallViewModel( val group: InstallStepGroup, @StringRes - val nameResId: Int + val nameResId: Int, ) { // Kotlin FETCH_KT_VERSION(InstallStepGroup.APK_DL, R.string.install_step_fetch_kt_version), diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt new file mode 100644 index 0000000..9362c51 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt @@ -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? = null, +) : Parcelable + +class InstallScreen(val data: InstallData) : Screen { + override val key = "Install" + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val model = getScreenModel(parameters = { parametersOf(data) }) + + var expandedGroup by remember { mutableStateOf(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)) + } + } + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt similarity index 89% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt index 4240938..336910a 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt @@ -1,9 +1,9 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.screens.plugins import android.util.Log import androidx.compose.runtime.* -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.BuildConfig import com.aliucord.manager.aliucordDir import com.aliucord.manager.domain.model.Plugin @@ -11,8 +11,7 @@ import kotlinx.coroutines.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* -@OptIn(ExperimentalSerializationApi::class) -class PluginsViewModel : ViewModel() { +class PluginsModel : ScreenModel { var showChangelogDialog by mutableStateOf(false) private set @@ -35,10 +34,6 @@ class PluginsViewModel : ViewModel() { this.search = search } - fun clearSearch() { - search = "" - } - fun showChangelogDialog(plugin: Plugin) { showChangelogDialog = true selectedPlugin = plugin @@ -65,13 +60,14 @@ class PluginsViewModel : ViewModel() { private val settingsFile = aliucordDir.resolve("settings/Aliucord.json") fun setPluginEnabled(plugin: String, enable: Boolean) { - viewModelScope.launch(Dispatchers.IO) { + screenModelScope.launch(Dispatchers.IO) { withContext(Dispatchers.Main) { enabled.clear() enabled.putAll(loadEnabled()) enabled[plugin] = enable } + @OptIn(ExperimentalSerializationApi::class) try { val settings = settingsFile.inputStream() .use { Json.decodeFromStream>(it) } @@ -91,6 +87,7 @@ class PluginsViewModel : ViewModel() { private suspend fun loadEnabled(): Map = withContext(Dispatchers.IO) { if (!settingsFile.exists()) return@withContext emptyMap() + @OptIn(ExperimentalSerializationApi::class) try { settingsFile.inputStream() .use { Json.decodeFromStream>(it) } @@ -105,11 +102,11 @@ class PluginsViewModel : ViewModel() { } init { - viewModelScope.launch { + screenModelScope.launch { enabled.putAll(loadEnabled()) } - viewModelScope.launch(Dispatchers.IO) { + screenModelScope.launch(Dispatchers.IO) { val pluginsDir = aliucordDir.resolve("plugins") if (!pluginsDir.exists() && !pluginsDir.mkdirs()) { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt new file mode 100644 index 0000000..191b810 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt @@ -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() + + 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)) + } + } + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt similarity index 80% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt index e2c2ef6..5e8cef5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt @@ -1,20 +1,20 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.screens.settings import android.app.Application import androidx.compose.runtime.* -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.R import com.aliucord.manager.domain.manager.PreferencesManager -import com.aliucord.manager.ui.theme.Theme +import com.aliucord.manager.ui.components.Theme import com.aliucord.manager.util.showToast import com.aliucord.manager.util.throttle -class SettingsViewModel( +class SettingsModel( private val application: Application, - val preferences: PreferencesManager -) : ViewModel() { - private val checkPackageNameThrottled = throttle(50L, viewModelScope) { + val preferences: PreferencesManager, +) : ScreenModel { + private val checkPackageNameThrottled = throttle(50L, screenModelScope) { packageNameError = !PACKAGE_REGEX.matches(preferences.packageName) } @@ -58,7 +58,7 @@ class SettingsViewModel( checkPackageNameThrottled() } - override fun onCleared() { + override fun onDispose() { if (!PACKAGE_REGEX.matches(preferences.packageName)) { preferences.packageName = "com.aliucord" application.showToast(R.string.settings_invalid_package_name) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..e72a34d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt @@ -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() + + 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)) + } + } + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt b/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt index 092b6a7..614f952 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt @@ -16,7 +16,7 @@ inline fun Iterable.joinToAnnotatedString( is Char -> append(it) else -> append(it.toString()) } - } + }, ) { prefix(this@Builder) for ((count, element) in this@joinToAnnotatedString.withIndex()) { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/UpdaterDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterDialog.kt similarity index 91% rename from app/src/main/kotlin/com/aliucord/manager/ui/dialog/UpdaterDialog.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterDialog.kt index 8bc37aa..61f38e1 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/dialog/UpdaterDialog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterDialog.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.dialog +package com.aliucord.manager.ui.widgets.updater import androidx.compose.foundation.layout.size import androidx.compose.material3.* @@ -8,12 +8,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import com.aliucord.manager.R -import com.aliucord.manager.ui.viewmodel.UpdaterViewModel import org.koin.androidx.compose.getViewModel @Composable fun UpdaterDialog( - viewModel: UpdaterViewModel = getViewModel() + viewModel: UpdaterViewModel = getViewModel(), ) { if (!viewModel.showDialog) return diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/UpdaterViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt similarity index 98% rename from app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/UpdaterViewModel.kt rename to app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt index 72d92c2..8ab960c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/UpdaterViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.viewmodel +package com.aliucord.manager.ui.widgets.updater import android.app.Application import androidx.compose.runtime.* diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Throttling.kt b/app/src/main/kotlin/com/aliucord/manager/util/Throttling.kt index 387d440..7dc33fe 100644 --- a/app/src/main/kotlin/com/aliucord/manager/util/Throttling.kt +++ b/app/src/main/kotlin/com/aliucord/manager/util/Throttling.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.* inline fun throttle( skipMs: Long, coroutineScope: CoroutineScope, - crossinline destinationFunction: suspend () -> Unit + crossinline destinationFunction: suspend () -> Unit, ): () -> Unit { var throttleJob: Job? = null return { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53e40a1..8d95777 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ kotlin = "1.9.22" kotlinx-immutable = "0.3.7" kotlinx-serialization = "1.5.0" ktor = "2.3.7" -navigation-reimagined = "1.3.0" # TODO: migrate to voyager +voyager = "1.0.0" zip = "2.1.0" [libraries] @@ -58,12 +58,16 @@ ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } ktor-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +# Voyager +voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } +voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } +voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" } + # Other apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" } axml = { module = "com.aliucord:axml", version.ref = "axml" } bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" } coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" } -navigationReimagined = { module = "dev.olshevski.navigation:reimagined", version.ref = "navigation-reimagined" } zip = { module = "io.github.diamondminer88:zip-android", version.ref = "zip" } # Only use the "aar" artifact [bundles] @@ -95,6 +99,11 @@ ktor = [ "ktor-content-negotiation", "ktor-serialization-json", ] +voyager = [ + "voyager-koin", + "voyager-navigator", + "voyager-transitions", +] [plugins] android-application = { id = "com.android.application", version.ref = "agp" }