diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index cbe3413..a8f37e1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,7 +1,3 @@
-val accompanistVersion = "0.26.1-alpha"
-val composeVersion = "1.3.0-alpha03"
-val ktorVersion = "2.1.0"
-
plugins {
id("com.android.application")
id("kotlin-android")
@@ -28,6 +24,7 @@ android {
buildTypes {
release {
isMinifyEnabled = true
+ isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
@@ -42,14 +39,6 @@ android {
freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
}
- applicationVariants.all {
- kotlin.sourceSets {
- getByName(name) {
- kotlin.srcDir("build/generated/ksp/$name/kotlin")
- }
- }
- }
-
buildFeatures.compose = true
composeOptions.kotlinCompilerExtensionVersion = "1.3.0"
}
@@ -68,6 +57,8 @@ dependencies {
implementation("androidx.activity:activity-compose:1.6.0-beta01")
// compose dependencies
+ val composeVersion = "1.3.0-alpha03"
+
implementation("androidx.compose.ui:ui:${composeVersion}")
implementation("androidx.compose.ui:ui-tooling:${composeVersion}")
implementation("androidx.compose.material:material-icons-extended:${composeVersion}")
@@ -76,9 +67,15 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0-alpha01")
// accompanist dependencies
+ val accompanistVersion = "0.26.1-alpha"
implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}")
implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}")
+ // Koin
+ val koinVersion = "3.2.0"
+ implementation("io.insert-koin:koin-android:$koinVersion")
+ implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
+
// other dependencies
implementation("io.coil-kt:coil-compose:2.1.0")
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
@@ -90,8 +87,9 @@ dependencies {
implementation("com.github.X1nto:Taxi:1.1.0")
// ktor
+ val ktorVersion = "2.1.0"
implementation("io.ktor:ktor-client-core:$ktorVersion")
- implementation("io.ktor:ktor-client-android:$ktorVersion")
+ implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8e24159..f713b06 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,12 +21,8 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
-
-
-
-
-
= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
.setData("package:com.aliucord.manager".toUri())
@@ -46,17 +46,12 @@ class MainActivity : ComponentActivity() {
setContent {
ManagerTheme(
- isBlack = Prefs.useBlack.get(),
- isDarkTheme = run {
- val theme = Theme.from(Prefs.theme.get())
-
- theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK
- }
+ isDarkTheme = preferences.theme == Theme.DARK || preferences.theme == Theme.SYSTEM && isSystemInDarkTheme(),
) {
val navigator = rememberBackstackNavigator(AppDestination.Home)
BackHandler {
- navigator.pop()
+ if (!navigator.pop()) finish()
}
Taxi(
@@ -66,11 +61,11 @@ class MainActivity : ComponentActivity() {
) { destination ->
when (destination) {
is AppDestination.Home -> MainRootScreen(
- navigator = navigator
- )
- is AppDestination.Plugins -> PluginsScreen(
- navigator = navigator
+ onClickInstall = { navigator.push(AppDestination.Install) },
+ onClickAbout = { navigator.push(AppDestination.About) },
+ onClickSettings = { navigator.push(AppDestination.Settings) }
)
+ is AppDestination.Plugins -> PluginsScreen()
is AppDestination.Install -> InstallerScreen(
onClickBack = navigator::pop,
)
@@ -80,9 +75,6 @@ class MainActivity : ComponentActivity() {
is AppDestination.About -> AboutScreen(
onClickBack = navigator::pop
)
- is AppDestination.Store -> StoreScreen(
- onClickBack = navigator::pop
- )
}
}
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt
new file mode 100644
index 0000000..169283d
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt
@@ -0,0 +1,17 @@
+package com.aliucord.manager
+
+import android.app.Application
+import com.aliucord.manager.di.*
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class ManagerApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+
+ startKoin {
+ androidContext(this@ManagerApplication)
+ modules(appModule, serviceModule, repositoryModule, viewModelModule, managerModule)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/di/AppModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/AppModule.kt
new file mode 100644
index 0000000..338f4da
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/di/AppModule.kt
@@ -0,0 +1,14 @@
+package com.aliucord.manager.di
+
+import kotlinx.serialization.json.Json
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.module
+
+val appModule = module {
+ fun provideJson(): Json = Json {
+ ignoreUnknownKeys = true
+ isLenient = true
+ }
+
+ singleOf(::provideJson)
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/di/ManagerModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/ManagerModule.kt
new file mode 100644
index 0000000..1a76776
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/di/ManagerModule.kt
@@ -0,0 +1,18 @@
+package com.aliucord.manager.di
+
+import android.content.Context
+import com.aliucord.manager.domain.manager.DownloadManager
+import com.aliucord.manager.domain.manager.PreferencesManager
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.module
+
+val managerModule = module {
+ fun providePreferences(context: Context): PreferencesManager {
+ return PreferencesManager(context.getSharedPreferences("preferences", Context.MODE_PRIVATE))
+ }
+
+ fun provideDownloadManager(context: Context) = DownloadManager(context)
+
+ singleOf(::providePreferences)
+ singleOf(::provideDownloadManager)
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/di/RepositoryModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/RepositoryModule.kt
new file mode 100644
index 0000000..43d7b88
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/di/RepositoryModule.kt
@@ -0,0 +1,9 @@
+package com.aliucord.manager.di
+
+import com.aliucord.manager.domain.repository.GithubRepository
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.module
+
+val repositoryModule = module {
+ singleOf(::GithubRepository)
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/di/ServiceModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/ServiceModule.kt
new file mode 100644
index 0000000..9d78e6b
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/di/ServiceModule.kt
@@ -0,0 +1,9 @@
+package com.aliucord.manager.di
+
+import com.aliucord.manager.network.service.GithubService
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.module
+
+val serviceModule = module {
+ singleOf(::GithubService)
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt
new file mode 100644
index 0000000..7c79b3b
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt
@@ -0,0 +1,13 @@
+package com.aliucord.manager.di
+
+import com.aliucord.manager.ui.viewmodel.*
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+
+val viewModelModule = module {
+ viewModelOf(::HomeViewModel)
+ viewModelOf(::PluginsViewModel)
+ viewModelOf(::AboutViewModel)
+ viewModelOf(::InstallViewModel)
+ viewModelOf(::SettingsViewModel)
+}
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
new file mode 100644
index 0000000..402e5c5
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt
@@ -0,0 +1,52 @@
+package com.aliucord.manager.domain.manager
+
+import android.app.DownloadManager
+import android.content.*
+import androidx.core.content.getSystemService
+import androidx.core.net.toUri
+import java.io.File
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class DownloadManager(
+ private val context: Context
+) {
+ private val downloadManager = context.getSystemService()!!
+
+ suspend fun downloadDiscordApk(version: String): File {
+ return download("$backendHost/download/discord?v=$version", "base-$version.apk")
+ }
+
+ suspend fun downloadSplit(version: String, split: String): File {
+ return download("$backendHost/download/discord?v=$version&split=$split", "$split-$version.apk")
+ }
+
+ private suspend fun download(url: String, fileName: String): File {
+ return suspendCoroutine { continuation ->
+ val receiver = object : BroadcastReceiver() {
+ var downloadId = 0L
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
+
+ if (downloadId == id) {
+ context.unregisterReceiver(this)
+ continuation.resume(context.filesDir)
+ }
+ }
+ }
+
+ context.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
+
+ receiver.downloadId = DownloadManager.Request(url.toUri()).run {
+ setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
+ setDestinationUri(context.externalCacheDir!!.resolve(fileName).toUri())
+ downloadManager.enqueue(this)
+ }
+ }
+ }
+
+ companion object {
+ private const val backendHost = "https://aliucord.com/"
+ }
+}
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
new file mode 100644
index 0000000..1f3c492
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/PreferencesManager.kt
@@ -0,0 +1,18 @@
+package com.aliucord.manager.domain.manager
+
+import android.content.SharedPreferences
+import com.aliucord.manager.aliucordDir
+import com.aliucord.manager.domain.manager.base.BasePreferenceManager
+import com.aliucord.manager.ui.theme.Theme
+
+class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager(preferences) {
+ var theme by enumPreference("theme", Theme.SYSTEM)
+ var replaceBg by booleanPreference("replace_bg", true)
+ var devMode by booleanPreference("dev_mode", false)
+ var debuggable by booleanPreference("debuggable", false)
+ var useDexFromStorage by booleanPreference("use_dex_from_storage", false)
+ var dexLocation by stringPreference("dex_location", "$aliucordDir/Injector.dex")
+ var appName by stringPreference("app_name", "Aliucord")
+ var packageName by stringPreference("package_name", "com.aliucord")
+ var version by stringPreference("version", "124206")
+}
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
new file mode 100644
index 0000000..84ad1a9
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/base/BasePreferencesManager.kt
@@ -0,0 +1,90 @@
+package com.aliucord.manager.domain.manager.base
+
+import android.content.SharedPreferences
+import androidx.compose.runtime.*
+import androidx.core.content.edit
+import kotlin.reflect.KProperty
+
+abstract class BasePreferenceManager(
+ 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)
+ private fun getInt(key: String, defaultValue: Int) = prefs.getInt(key, defaultValue)
+ private fun getFloat(key: String, defaultValue: Float) = prefs.getFloat(key, defaultValue)
+ protected inline fun > getEnum(key: String, defaultValue: E) =
+ enumValueOf(getString(key, defaultValue.name))
+
+ protected fun putString(key: String, value: String?) = prefs.edit { putString(key, value) }
+ private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) }
+ private fun putInt(key: String, value: Int) = prefs.edit { putInt(key, value) }
+ private fun putFloat(key: String, value: Float) = prefs.edit { putFloat(key, value) }
+ protected inline fun > putEnum(key: String, value: E) = putString(key, value.name)
+
+ protected class Preference(
+ private val key: String,
+ defaultValue: T,
+ getter: (key: String, defaultValue: T) -> T,
+ private val setter: (key: String, newValue: T) -> Unit
+ ) {
+ @Suppress("RedundantSetter")
+ var value by mutableStateOf(getter(key, defaultValue))
+ private set
+
+ operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
+ value = newValue
+ setter(key, newValue)
+ }
+ }
+
+ protected fun stringPreference(
+ key: String,
+ defaultValue: String
+ ) = Preference(
+ key = key,
+ defaultValue = defaultValue,
+ getter = ::getString,
+ setter = ::putString
+ )
+
+ protected fun booleanPreference(
+ key: String,
+ defaultValue: Boolean
+ ) = Preference(
+ key = key,
+ defaultValue = defaultValue,
+ getter = ::getBoolean,
+ setter = ::putBoolean
+ )
+
+ protected fun intPreference(
+ key: String,
+ defaultValue: Int
+ ) = Preference(
+ key = key,
+ defaultValue = defaultValue,
+ getter = ::getInt,
+ setter = ::putInt
+ )
+
+ protected fun floatPreference(
+ key: String,
+ defaultValue: Float
+ ) = Preference(
+ key = key,
+ defaultValue = defaultValue,
+ getter = ::getFloat,
+ setter = ::putFloat
+ )
+
+ protected inline fun > enumPreference(
+ key: String,
+ defaultValue: E
+ ) = Preference(
+ key = key,
+ defaultValue = defaultValue,
+ getter = ::getEnum,
+ setter = ::putEnum
+ )
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/model/Manifest.kt b/app/src/main/kotlin/com/aliucord/manager/domain/model/Manifest.kt
similarity index 59%
rename from app/src/main/kotlin/com/aliucord/manager/model/Manifest.kt
rename to app/src/main/kotlin/com/aliucord/manager/domain/model/Manifest.kt
index e17e44a..eaa305c 100644
--- a/app/src/main/kotlin/com/aliucord/manager/model/Manifest.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/model/Manifest.kt
@@ -3,9 +3,10 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.model
+package com.aliucord.manager.domain.model
import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonObject
@Serializable
data class Manifest(
@@ -15,8 +16,13 @@ data class Manifest(
val version: String,
val updateUrl: String,
val changelog: String?,
- val changelogMedia: String?
+ val changelogMedia: String?,
+ val links: JsonObject
) {
+ val repositoryUrl = updateUrl
+ .replace("raw.githubusercontent.com", "github.com")
+ .replaceFirst("/builds.*".toRegex(), "")
+
@Serializable
data class Author(val name: String, val id: Long)
}
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
new file mode 100644
index 0000000..5c797bb
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/model/Plugin.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 Juby210 & zt
+ * Licensed under the Open Software License version 3.0
+ */
+
+package com.aliucord.manager.domain.model
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
+import java.io.File
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
+
+private val json = Json {
+ ignoreUnknownKeys = true
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+data class Plugin(val file: File) {
+ val manifest: Manifest
+
+ init {
+ ZipFile(file).use { zipFile ->
+ val entry = zipFile.getEntry("manifest.json")
+ ?: throw ZipException("Plugin ${file.nameWithoutExtension} has no manifest.")
+
+ manifest = zipFile.getInputStream(entry).use { zis -> json.decodeFromStream(zis) }
+ }
+ }
+}
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
new file mode 100644
index 0000000..adda499
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt
@@ -0,0 +1,19 @@
+package com.aliucord.manager.domain.repository
+
+import com.aliucord.manager.network.dto.GithubUser
+import com.aliucord.manager.network.dto.Version
+import com.aliucord.manager.network.service.GithubService
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+
+class GithubRepository(
+ private val json: Json,
+ private val service: GithubService
+) {
+ suspend fun getCommits(page: Int) = service.getCommits(page)
+
+ suspend fun getContributors() = service.getContributors()
+ .sortedByDescending(GithubUser::contributions)
+
+ suspend fun getVersion() = json.decodeFromString(service.getVersion())
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt
index e232f02..6a78fda 100644
--- a/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt
@@ -6,28 +6,30 @@ import android.content.pm.PackageInstaller
import android.os.IBinder
import android.util.Log
import android.widget.Toast
+import com.aliucord.manager.BuildConfig
class InstallService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when (val statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
- val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT)
- confirmationIntent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ @Suppress("DEPRECATION") // No.
+ val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT)!!
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(confirmationIntent)
}
PackageInstaller.STATUS_SUCCESS -> Toast.makeText(
- this,
- "Successfully installed Aliucord!",
- Toast.LENGTH_LONG
+ /* context = */ this,
+ /* text = */ "Successfully installed Aliucord!",
+ /* duration = */ Toast.LENGTH_LONG
).show()
PackageInstaller.STATUS_FAILURE_ABORTED -> Toast.makeText(
- this,
- "Aborted installation",
- Toast.LENGTH_LONG
+ /* context = */ this,
+ /* text = */ "Aborted installation",
+ /* duration = */ Toast.LENGTH_LONG
).show()
else -> {
- Log.i("Installer", "Installation failed: error code $statusCode")
+ Log.i(BuildConfig.TAG, "Installation failed: error code $statusCode")
Toast.makeText(this, "Failed: Error $statusCode", Toast.LENGTH_LONG).show()
}
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt
deleted file mode 100644
index 6855bd4..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.installer.util
-
-import android.app.DownloadManager
-import android.content.*
-import androidx.core.net.toUri
-import java.io.File
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-
-object DownloadUtils {
- private const val backendHost = "https://aliucord.com/"
-
- suspend fun downloadDiscordApk(ctx: Context, version: String) =
- download(ctx, "$backendHost/download/discord?v=$version", "base-$version.apk")
-
- suspend fun downloadSplit(ctx: Context, version: String, split: String) =
- download(ctx, "$backendHost/download/discord?v=$version&split=$split", "$split-$version.apk")
-
- private suspend fun download(ctx: Context, url: String, fileName: String): File {
- val downloadManager = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
-
- return suspendCoroutine { continuation ->
- val receiver = object : BroadcastReceiver() {
- var downloadId = 0L
-
- override fun onReceive(context: Context, intent: Intent) {
- val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
-
- if (downloadId == id) {
- ctx.unregisterReceiver(this)
- continuation.resume(context.filesDir)
- }
- }
- }
-
- ctx.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
-
- receiver.downloadId = DownloadManager.Request(url.toUri()).run {
- setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
- setDestinationUri(ctx.externalCacheDir!!.resolve(fileName).toUri())
- downloadManager.enqueue(this)
- }
- }
- }
-}
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 518b11d..d15d60f 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
@@ -5,7 +5,6 @@
package com.aliucord.manager.installer.util
-import com.aliucord.manager.preferences.Prefs
import pxb.android.axml.*
object ManifestPatcher {
@@ -15,8 +14,12 @@ object ManifestPatcher {
private const val DEBUGGABLE = "debuggable"
private const val LABEL = "label"
- fun patchManifest(manifestBytes: ByteArray): ByteArray {
- val packageName = Prefs.packageName.get()
+ fun patchManifest(
+ manifestBytes: ByteArray,
+ packageName: String,
+ appName: String,
+ debuggable: Boolean
+ ): ByteArray {
val reader = AxmlReader(manifestBytes)
val writer = AxmlWriter()
@@ -38,12 +41,12 @@ object ManifestPatcher {
"application" -> object : ReplaceAttrsVisitor(
nv,
mapOf(
- LABEL to Prefs.appName.get(),
- DEBUGGABLE to Prefs.debuggable.get(),
+ LABEL to appName,
+ DEBUGGABLE to debuggable,
USES_CLEARTEXT_TRAFFIC to true
)
) {
- private var addDebuggable = Prefs.debuggable.get()
+ private var addDebuggable = debuggable
private var addUseClearTextTraffic = true
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
@@ -56,12 +59,15 @@ object ManifestPatcher {
val visitor = super.child(ns, name)
return when (name) {
- "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to Prefs.appName.get()))
+ "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
"provider" -> object : NodeVisitor(visitor) {
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
super.attr(
- ns, name, resourceId, type,
- if (name == "authorities") (value as String).replace("com.discord", packageName) else value
+ /* ns = */ ns,
+ /* name = */ name,
+ /* resourceId = */ resourceId,
+ /* type = */ type,
+ /* obj = */ if (name == "authorities") (value as String).replace("com.discord", packageName) else value
)
}
}
@@ -91,16 +97,20 @@ object ManifestPatcher {
return writer.toByteArray()
}
- fun renamePackage(manifestBytes: ByteArray): ByteArray {
- val packageName = Prefs.packageName.get()
+ fun renamePackage(
+ manifestBytes: ByteArray,
+ packageName: String
+ ): ByteArray {
val reader = AxmlReader(manifestBytes)
val writer = AxmlWriter()
- reader.accept(object : AxmlVisitor(writer) {
- override fun child(ns: String?, name: String?): ReplaceAttrsVisitor {
- return ReplaceAttrsVisitor(super.child(ns, name), mapOf("package" to packageName))
+ reader.accept(
+ object : AxmlVisitor(writer) {
+ override fun child(ns: String?, name: String?): ReplaceAttrsVisitor {
+ return ReplaceAttrsVisitor(super.child(ns, name), mapOf("package" to packageName))
+ }
}
- })
+ )
return writer.toByteArray()
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Signer.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt
similarity index 78%
rename from app/src/main/kotlin/com/aliucord/manager/util/Signer.kt
rename to app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt
index c8526b1..2d9e32f 100644
--- a/app/src/main/kotlin/com/aliucord/manager/util/Signer.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt
@@ -3,8 +3,9 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.util
+package com.aliucord.manager.installer.util
+import com.aliucord.manager.aliucordDir
import com.android.apksig.ApkSigner
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
@@ -34,9 +35,9 @@ object Signer {
val certificate = keyStore.getCertificate(alias) as X509Certificate
ApkSigner.SignerConfig.Builder(
- "Aliucord Manager signer",
- keyStore.getKey(alias, password) as PrivateKey,
- listOf(certificate),
+ /* name = */ "Aliucord Manager signer",
+ /* privateKey = */ keyStore.getKey(alias, password) as PrivateKey,
+ /* certificates = */ listOf(certificate),
).build()
}
@@ -62,13 +63,13 @@ object Signer {
generateKeyPair()
}
val builder = X509v3CertificateBuilder(
- x500Name,
- serialNumber,
- Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
- Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
- Locale.ENGLISH,
- x500Name,
- SubjectPublicKeyInfo.getInstance(pair.public.encoded)
+ /* issuer = */ x500Name,
+ /* serial = */ serialNumber,
+ /* notBefore = */ Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
+ /* notAfter = */ Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
+ /* dateLocale = */ Locale.ENGLISH,
+ /* subject = */ x500Name,
+ /* publicKeyInfo = */ SubjectPublicKeyInfo.getInstance(pair.public.encoded)
)
val signer = JcaContentSignerBuilder("SHA1withRSA").build(pair.private)
diff --git a/app/src/main/kotlin/com/aliucord/manager/util/ZipUtils.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt
similarity index 90%
rename from app/src/main/kotlin/com/aliucord/manager/util/ZipUtils.kt
rename to app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt
index 0a7f4f2..961f5b8 100644
--- a/app/src/main/kotlin/com/aliucord/manager/util/ZipUtils.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt
@@ -3,7 +3,7 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.util
+package com.aliucord.manager.installer.util
import com.aliucord.libzip.Zip
import java.io.InputStream
diff --git a/app/src/main/kotlin/com/aliucord/manager/model/Plugin.kt b/app/src/main/kotlin/com/aliucord/manager/model/Plugin.kt
deleted file mode 100644
index ce2ef84..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/model/Plugin.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.model
-
-import android.util.Log
-import com.aliucord.manager.BuildConfig
-import com.aliucord.manager.util.aliucordDir
-import com.aliucord.manager.util.json
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.json.decodeFromStream
-import java.io.File
-import java.util.zip.ZipException
-import java.util.zip.ZipFile
-
-@OptIn(ExperimentalSerializationApi::class)
-data class Plugin(val file: File) {
- val manifest: Manifest
-
- init {
- ZipFile(file).use { zipFile ->
- val entry = zipFile.getEntry("manifest.json")
- ?: throw ZipException("Plugin ${file.nameWithoutExtension} has no manifest.")
-
- manifest = zipFile.getInputStream(entry).use { zis -> json.decodeFromStream(zis) }
- }
- }
-
- companion object {
- private val pluginsDir = aliucordDir.resolve("plugins")
-
- fun loadAll(): List {
- if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
- Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?")
- return emptyList()
- }
-
- val files = pluginsDir.listFiles() ?: return emptyList()
-
- return files.mapNotNull { file ->
- if (file.extension == "zip") {
- try {
- Plugin(file)
- } catch (th: Throwable) {
- Log.e(BuildConfig.TAG, "Failed to load plugin ${file.nameWithoutExtension}", th)
- null
- }
- } else {
- Log.w(BuildConfig.TAG, "Found non zip ${file.name} in plugins folder.")
- null
- }
- }.sortedBy { it.manifest.name }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/model/github/Commit.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt
similarity index 65%
rename from app/src/main/kotlin/com/aliucord/manager/model/github/Commit.kt
rename to app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt
index 01212fc..8521794 100644
--- a/app/src/main/kotlin/com/aliucord/manager/model/github/Commit.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt
@@ -3,7 +3,7 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.model.github
+package com.aliucord.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -16,6 +16,8 @@ data class Commit(
val commit: Commit,
val author: Author
) {
- @Serializable data class Author(@SerialName("login") val name: String)
- @Serializable data class Commit(val message: String)
+ @Serializable
+ data class Author(@SerialName("login") val name: String)
+ @Serializable
+ data class Commit(val message: String)
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/model/github/GithubUser.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt
similarity index 87%
rename from app/src/main/kotlin/com/aliucord/manager/model/github/GithubUser.kt
rename to app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt
index 62b52ad..331544f 100644
--- a/app/src/main/kotlin/com/aliucord/manager/model/github/GithubUser.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/GithubUser.kt
@@ -3,7 +3,7 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.model.github
+package com.aliucord.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/app/src/main/kotlin/com/aliucord/manager/model/github/Version.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt
similarity index 86%
rename from app/src/main/kotlin/com/aliucord/manager/model/github/Version.kt
rename to app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt
index 2c0e69e..fdf0b14 100644
--- a/app/src/main/kotlin/com/aliucord/manager/model/github/Version.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/Version.kt
@@ -3,7 +3,7 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.model.github
+package com.aliucord.manager.network.dto
import kotlinx.serialization.Serializable
diff --git a/app/src/main/kotlin/com/aliucord/manager/network/service/GithubService.kt b/app/src/main/kotlin/com/aliucord/manager/network/service/GithubService.kt
new file mode 100644
index 0000000..7915f70
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/network/service/GithubService.kt
@@ -0,0 +1,47 @@
+package com.aliucord.manager.network.service
+
+import com.aliucord.manager.network.dto.Commit
+import com.aliucord.manager.network.dto.GithubUser
+import io.ktor.client.*
+import io.ktor.client.call.*
+import io.ktor.client.engine.cio.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.json.Json
+
+class GithubService(
+ private val json: Json
+) {
+ private val httpClient = HttpClient(CIO) {
+ install(ContentNegotiation) {
+ json(json)
+ }
+ }
+
+ suspend fun getCommits(page: Int): List = withContext(Dispatchers.IO) {
+ httpClient.get(commitsUrl) {
+ parameter("page", page)
+ }.body()
+ }
+
+ suspend fun getVersion(): String = withContext(Dispatchers.IO) {
+ httpClient.get(dataUrl).bodyAsText()
+ }
+
+ suspend fun getContributors(): List = withContext(Dispatchers.IO) {
+ httpClient.get(contributorsUrl).body()
+ }
+
+ companion object {
+ private const val org = "Aliucord"
+ private const val repo = "Aliucord"
+
+ private const val commitsUrl = "https://api.github.com/repos/$org/$repo/commits"
+ private const val contributorsUrl = "https://api.github.com/repos/$org/$repo/contributors"
+ private const val dataUrl = "https://raw.githubusercontent.com/$org/$repo/builds/data.json"
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/preferences/Preferences.kt b/app/src/main/kotlin/com/aliucord/manager/preferences/Preferences.kt
deleted file mode 100644
index 16fb3d8..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/preferences/Preferences.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-
-package com.aliucord.manager.preferences
-
-import android.content.SharedPreferences
-import androidx.compose.runtime.mutableStateOf
-import androidx.core.content.edit
-import kotlin.reflect.KProperty
-
-lateinit var sharedPreferences: SharedPreferences
-
-class Preference(
- private val key: String,
- defaultValue: T,
- getter: SharedPreferences.(key: String, defaultValue: T) -> T?,
- private val setter: SharedPreferences.Editor.(key: String, newValue: T) -> Unit
-) {
- private val value = mutableStateOf(sharedPreferences.getter(key, defaultValue) ?: defaultValue)
-
- operator fun getValue(thisRef: Any?, property: KProperty<*>) = value.value
-
- operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) = set(newValue)
-
- fun get() = value.value
-
- fun set(newValue: T) {
- if (value.value == newValue) return
-
- value.value = newValue
-
- sharedPreferences.edit { setter(key, newValue) }
- }
-}
-
-fun boolPreference(
- key: String,
- defaultValue: Boolean = false
-) = Preference(
- key,
- defaultValue,
- SharedPreferences::getBoolean,
- SharedPreferences.Editor::putBoolean
-)
-
-fun intPreference(
- key: String,
- defaultValue: Int = 0
-) = Preference(
- key,
- defaultValue,
- SharedPreferences::getInt,
- SharedPreferences.Editor::putInt
-)
-
-fun stringPreference(
- key: String,
- defaultValue: String
-) = Preference(
- key,
- defaultValue,
- SharedPreferences::getString,
- SharedPreferences.Editor::putString
-)
diff --git a/app/src/main/kotlin/com/aliucord/manager/preferences/PreferencesHolder.kt b/app/src/main/kotlin/com/aliucord/manager/preferences/PreferencesHolder.kt
deleted file mode 100644
index f9ad786..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/preferences/PreferencesHolder.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.preferences
-
-import com.aliucord.manager.util.aliucordDir
-
-object Prefs {
- val theme = intPreference("theme")
- val useBlack = boolPreference("use_black")
- val replaceBg = boolPreference("replace_bg", true)
- val devMode = boolPreference("dev_mode", false)
- val debuggable = boolPreference("debuggable")
- val useDexFromStorage = boolPreference("use_dex_from_storage")
- val dexLocation = stringPreference("dex_location", "$aliucordDir/Injector.dex")
- val appName = stringPreference("app_name", "Aliucord")
- val packageName = stringPreference("package_name", "com.aliucord")
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorsCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt
similarity index 72%
rename from app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorsCard.kt
rename to app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt
index f8ee871..f61a604 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorsCard.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt
@@ -3,26 +3,27 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.ui.component.about
+package com.aliucord.manager.ui.component
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.*
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
import com.aliucord.manager.R
-import com.aliucord.manager.model.github.GithubUser
+import com.aliucord.manager.network.dto.GithubUser
@Composable
fun ContributorsCard(contributors: List) {
- ElevatedCard(
- modifier = Modifier
- .wrapContentHeight()
- .fillMaxWidth()
- ) {
+ ElevatedCard {
Column(
modifier = Modifier
.padding(16.dp)
@@ -56,3 +57,17 @@ fun ContributorsCard(contributors: List) {
}
}
}
+
+@Composable
+fun ContributorEntry(name: String) {
+ val uriHandler = LocalUriHandler.current
+
+ AsyncImage(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape)
+ .clickable { uriHandler.openUri("https://github.com/$name") },
+ model = "https://github.com/$name.png",
+ contentDescription = name
+ )
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/GrantPermission.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/GrantPermission.kt
index b16590e..98fccc9 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/GrantPermission.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/GrantPermission.kt
@@ -32,7 +32,7 @@ fun GrantPermission(permissionState: PermissionState) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
- stringResource(R.string.permission_required),
+ text = stringResource(R.string.permission_required),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)
@@ -42,7 +42,7 @@ fun GrantPermission(permissionState: PermissionState) {
Row {
val context = LocalContext.current
- Button(onClick = { permissionState.launchPermissionRequest() }) {
+ Button(onClick = permissionState::launchPermissionRequest) {
Text(stringResource(R.string.permission_grant))
}
@@ -52,15 +52,11 @@ fun GrantPermission(permissionState: PermissionState) {
onClick = {
context.startActivity(
Intent(
- Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
+ /* action = */ Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ /* uri = */ Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
)
)
- },
- colors = ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.secondaryContainer,
- contentColor = MaterialTheme.colorScheme.onSecondaryContainer
- ),
+ }
) {
Text(stringResource(R.string.open_settings))
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallerDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/InstallerDialog.kt
similarity index 77%
rename from app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallerDialog.kt
rename to app/src/main/kotlin/com/aliucord/manager/ui/component/InstallerDialog.kt
index 6e270b1..8f40ed4 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallerDialog.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/InstallerDialog.kt
@@ -3,15 +3,17 @@
* Licensed under the Open Software License version 3.0
*/
-package com.aliucord.manager.ui.component.installer
+package com.aliucord.manager.ui.component
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Download
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 com.aliucord.manager.R
@@ -22,34 +24,27 @@ fun InstallerDialog(
onDismissRequest: () -> Unit,
onConfirm: (DownloadMethod) -> Unit
) {
- var selectedMethod by remember { mutableStateOf(DownloadMethod.DOWNLOAD) }
+ var selectedMethod by rememberSaveable { mutableStateOf(DownloadMethod.DOWNLOAD) }
AlertDialog(
icon = {
Icon(
- painter = painterResource(id = R.drawable.ic_download_24dp),
+ imageVector = Icons.Default.Download,
contentDescription = stringResource(R.string.download_method)
)
},
title = {
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(stringResource(R.string.download_method))
- }
+ Text(stringResource(R.string.download_method))
},
text = {
Column {
mapOf(
DownloadMethod.DOWNLOAD to stringResource(R.string.download_apk),
- DownloadMethod.SELECT to stringResource(R.string.select_apk)
+ // DownloadMethod.SELECT to stringResource(R.string.select_apk)
).forEach { (method, name) ->
Column {
Row(
- modifier = Modifier.clickable {
- selectedMethod = method
- },
+ modifier = Modifier.clickable { selectedMethod = method },
verticalAlignment = Alignment.CenterVertically
) {
Text(
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorEntry.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorEntry.kt
deleted file mode 100644
index 9ac9792..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/ContributorEntry.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.about
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
-import coil.compose.AsyncImage
-import com.aliucord.manager.R
-
-@Composable
-fun ContributorEntry(name: String) {
- val uriHandler = LocalUriHandler.current
-
- AsyncImage(
- modifier = Modifier
- .fillMaxSize()
- .clip(CircleShape)
- .clickable { uriHandler.openUri("https://github.com/$name") },
- model = "https://github.com/$name.png",
- contentDescription = stringResource(R.string.contributor)
- )
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/TeamCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/about/TeamCard.kt
deleted file mode 100644
index 242e3df..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/TeamCard.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.about
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.aliucord.manager.R
-
-@Composable
-fun TeamCard() = ElevatedCard(
- modifier = Modifier.wrapContentHeight().fillMaxWidth()
-) {
- Column(
- modifier = Modifier.padding(16.dp).fillMaxWidth(),
- verticalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- Text(
- text = stringResource(R.string.team),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.primary
- )
-
- Column(
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- UserEntry("Juby210", "Fox")
- UserEntry("Vendicated", "the ven")
- }
- }
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/UserEntry.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/about/UserEntry.kt
deleted file mode 100644
index 09eed86..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/about/UserEntry.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.about
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
-
-@Composable
-fun UserEntry(name: String, roles: String) = Column {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- AsyncImage(
- modifier = Modifier
- .size(54.dp)
- .clip(CircleShape),
- model = "https://github.com/$name.png",
- contentDescription = null
- )
- Column {
- Text(
- text = name,
- style = MaterialTheme.typography.titleLarge
- )
- Text(roles)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt
index f555592..57c3cce 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt
@@ -5,38 +5,32 @@
package com.aliucord.manager.ui.component.plugins
-import android.annotation.SuppressLint
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.ClickableText
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.History
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.vector.rememberVectorPainter
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
-import coil.request.ImageRequest
import com.aliucord.manager.R
-import com.aliucord.manager.model.Plugin
+import com.aliucord.manager.domain.model.Plugin
private val hyperLinkPattern = Regex("\\[(.+?)]\\((.+?\\))")
@Suppress("RegExpRedundantEscape") // It is very much not redundant and causes a crash lol
private val headerStylePattern = Regex("\\{(improved|added|fixed)( marginTop)?\\}")
-@SuppressLint("ComposableNaming") // Can't use MaterialTheme without Composable, but this is not a component
@Composable
private fun AnnotatedString.Builder.hyperlink(content: String) {
var idx = 0
@@ -51,7 +45,7 @@ private fun AnnotatedString.Builder.hyperlink(content: String) {
append(content.substring(idx, start))
withStyle(
- style = SpanStyle(
+ SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
)
@@ -77,9 +71,8 @@ fun Changelog(
onDismissRequest = onDismiss,
icon = {
Icon(
- painter = painterResource(R.drawable.ic_history_white_24dp),
- contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name),
- tint = MaterialTheme.colorScheme.onSurface
+ imageVector = Icons.Default.History,
+ contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name)
)
},
title = { Text(plugin.manifest.name) },
@@ -91,17 +84,12 @@ fun Changelog(
.fillMaxWidth()
.heightIn(max = 90.dp)
.clip(RoundedCornerShape(14.dp)),
- model = ImageRequest.Builder(LocalContext.current)
- .data(mediaUrl)
- .build(),
- placeholder = rememberVectorPainter(image = Icons.Default.Person),
+ model = mediaUrl,
contentDescription = stringResource(R.string.changelog_media)
)
}
- LazyColumn(
- modifier = Modifier.wrapContentHeight()
- ) {
+ LazyColumn {
items(plugin.manifest.changelog!!.lines()) {
var line = it.trim()
@@ -113,16 +101,17 @@ fun Changelog(
} while (line.startsWith("#"))
Text(
- line.trimStart(),
+ text = line.trimStart(),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
'*' -> {
LinkText(
- buildAnnotatedString {
+ modifier = Modifier.padding(bottom = 2.dp),
+ annotatedString = buildAnnotatedString {
withStyle(
- style = SpanStyle(
+ SpanStyle(
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
@@ -131,26 +120,25 @@ fun Changelog(
}
hyperlink(line.substring(1))
- },
- Modifier.padding(bottom = 2.dp)
+ }
)
}
else -> {
when {
line.endsWith("marginTop}") -> {
val color = MaterialTheme.colorScheme.onSurface
+
Text(
- line,
+ text = line,
fontWeight = FontWeight.Bold,
color = color,
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
- line.all { c -> c == '=' } -> {
- } // Discord ignores =======
+ line.all { c -> c == '=' } -> { } // Discord ignores =======
else -> {
LinkText(
- buildAnnotatedString {
+ annotatedString = buildAnnotatedString {
hyperlink(line)
}
)
@@ -164,9 +152,7 @@ fun Changelog(
}
},
confirmButton = {
- Button(
- onClick = onDismiss
- ) {
+ Button(onClick = onDismiss) {
Text(stringResource(R.string.close))
}
}
@@ -174,17 +160,20 @@ fun Changelog(
}
@Composable
-private fun LinkText(annotatedString: AnnotatedString, modifier: Modifier = Modifier) {
+private fun LinkText(
+ modifier: Modifier = Modifier,
+ annotatedString: AnnotatedString
+) {
val urlHandler = LocalUriHandler.current
ClickableText(
+ modifier = modifier,
text = annotatedString,
style = TextStyle(color = LocalContentColor.current),
onClick = { offset ->
annotatedString.getStringAnnotations(offset, offset).firstOrNull()?.let {
urlHandler.openUri(it.item)
}
- },
- modifier = modifier
+ }
)
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt
index 1dbe804..5239aa3 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt
@@ -5,13 +5,15 @@
package com.aliucord.manager.ui.component.plugins
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.History
import androidx.compose.material3.*
import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalUriHandler
@@ -21,29 +23,26 @@ import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.aliucord.manager.R
-import com.aliucord.manager.model.Plugin
+import com.aliucord.manager.domain.model.Plugin
@Composable
fun PluginCard(
plugin: Plugin,
- onDelete: () -> Unit,
- onShowChangelog: () -> Unit
+ onClickDelete: () -> Unit,
+ onClickShowChangelog: () -> Unit
) {
- val uriHandler = LocalUriHandler.current
-
- ElevatedCard(
- modifier = Modifier.fillMaxWidth()
- ) {
+ ElevatedCard {
Column(
- modifier = Modifier.padding(16.dp)
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
) {
- var isEnabled by remember { mutableStateOf(true) }
+ val uriHandler = LocalUriHandler.current
+ var isEnabled by rememberSaveable { mutableStateOf(true) }
// Header
Row(
- modifier = Modifier.toggleable(value = isEnabled) {
- isEnabled = it
- }
+ modifier = Modifier.clickable { isEnabled = !isEnabled }
) {
Column {
// Name
@@ -79,16 +78,14 @@ fun PluginCard(
}
}
}
+
ClickableText(
text = authors,
style = MaterialTheme.typography.labelMedium,
- onClick = {
- authors.getStringAnnotations("authorId", it, it)
+ onClick = { offset ->
+ authors.getStringAnnotations("authorId", offset, offset)
.firstOrNull()
- ?.let { (id) ->
- val url = "https://discord.com/users/$id"
- uriHandler.openUri(url)
- }
+ ?.let { (id) -> uriHandler.openUri("https://discord.com/users/$id") }
}
)
}
@@ -102,15 +99,15 @@ fun PluginCard(
)
}
- Divider(modifier = Modifier.alpha(0.1f))
+ Divider(modifier = Modifier.alpha(0.3f))
// Description
Text(
- text = plugin.manifest.description,
- style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.heightIn(max = 150.dp, min = 40.dp)
.padding(top = 10.dp),
+ text = plugin.manifest.description,
+ style = MaterialTheme.typography.bodyMedium
)
Row(
@@ -118,23 +115,19 @@ fun PluginCard(
) {
IconButton(
onClick = {
- uriHandler.openUri(
- plugin.manifest.updateUrl
- .replace("raw.githubusercontent.com", "github.com")
- .replaceFirst("/builds.*".toRegex(), "")
- )
+ uriHandler.openUri(plugin.manifest.repositoryUrl)
}
) {
Icon(
painter = painterResource(R.drawable.ic_account_github_white_24dp),
- contentDescription = "GitHub"
+ contentDescription = stringResource(R.string.github)
)
}
if (plugin.manifest.changelog != null) {
- IconButton(onClick = onShowChangelog) {
+ IconButton(onClick = onClickShowChangelog) {
Icon(
- painter = painterResource(R.drawable.ic_history_white_24dp),
+ imageVector = Icons.Default.History,
contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name)
)
}
@@ -142,12 +135,10 @@ fun PluginCard(
Spacer(Modifier.weight(1f, true))
- IconButton(
- onClick = onDelete
- ) {
+ IconButton(onClick = onClickDelete) {
Icon(
imageVector = Icons.Default.Delete,
- contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name),
+ contentDescription = stringResource(R.string.uninstall),
tint = MaterialTheme.colorScheme.error
)
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/Setting.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/Setting.kt
deleted file mode 100644
index 0dc3c10..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/Setting.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.settings
-
-import androidx.compose.foundation.clickable
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.aliucord.manager.preferences.Preference
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun PreferenceItem(
- icon: @Composable (() -> Unit)? = null,
- title: @Composable () -> Unit,
- description: @Composable (() -> Unit)? = null,
- preference: Preference
-) = ListItem(
- modifier = Modifier.clickable {
- preference.set(!preference.get())
- },
- leadingContent = icon,
- headlineText = title,
- supportingText = description,
- trailingContent = {
- Switch(
- checked = preference.get(),
- onCheckedChange = preference::set
- )
- }
-)
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeDialog.kt
deleted file mode 100644
index 737f7b4..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeDialog.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.settings
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Style
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import com.aliucord.manager.R
-import com.aliucord.manager.preferences.Prefs
-import com.aliucord.manager.ui.theme.Theme
-
-@Composable
-fun ThemeDialog(
- onDismissRequest: () -> Unit,
- onConfirm: (Theme) -> Unit
-) {
- var selectedTheme by remember { mutableStateOf(Theme.from(Prefs.theme.get())) }
-
- AlertDialog(
- onDismissRequest = onDismissRequest,
- icon = {
- Icon(
- imageVector = Icons.Default.Style,
- contentDescription = "Theme"
- )
- },
- title = { Text(stringResource(R.string.theme)) },
- text = {
- Column {
- Theme.values().forEach { theme ->
- Row(
- modifier = Modifier.clickable {
- selectedTheme = theme
- },
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- theme.displayName,
- 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.apply))
- }
- }
- )
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeSetting.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeSetting.kt
deleted file mode 100644
index 9d26afb..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/ThemeSetting.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.ui.component.settings
-
-import androidx.compose.foundation.clickable
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import com.aliucord.manager.R
-import com.aliucord.manager.preferences.Prefs
-import com.aliucord.manager.ui.theme.Theme
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ThemeSetting(
- onClick: () -> Unit,
-) = ListItem(
- modifier = Modifier.clickable { onClick() },
- headlineText = { Text(stringResource(R.string.theme)) },
- supportingText = { Text(stringResource(R.string.theme_setting_description)) },
- trailingContent = {
- FilledTonalButton(
- onClick = onClick
- ) {
- Text(Theme.from(Prefs.theme.get()).displayName)
- }
- }
-)
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
index 59ac561..0abe16d 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt
@@ -23,9 +23,6 @@ sealed interface AppDestination : Destination {
@Parcelize
object Install : AppDestination
- @Parcelize
- object Store : AppDestination
-
@Parcelize
object About : AppDestination
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
index 4718d9b..ad15ee5 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt
@@ -6,24 +6,30 @@
package com.aliucord.manager.ui.screen
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateBefore
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.AsyncImage
import com.aliucord.manager.R
-import com.aliucord.manager.ui.component.about.ContributorsCard
-import com.aliucord.manager.ui.component.about.TeamCard
+import com.aliucord.manager.ui.component.ContributorsCard
import com.aliucord.manager.ui.viewmodel.AboutViewModel
+import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AboutScreen(onClickBack: () -> Unit) {
+fun AboutScreen(
+ viewModel: AboutViewModel = getViewModel(),
+ onClickBack: () -> Unit
+) {
Scaffold(
topBar = {
MediumTopAppBar(
@@ -40,14 +46,35 @@ fun AboutScreen(onClickBack: () -> Unit) {
}
) { paddingValues ->
Column(
- verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(paddingValues)
- .padding(horizontal = 12.dp, vertical = 14.dp)
+ .padding(horizontal = 12.dp, vertical = 14.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val uriHandler = LocalUriHandler.current
- val viewModel: AboutViewModel = viewModel()
- TeamCard()
+
+ ElevatedCard {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.team),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.primary
+ )
+
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ UserEntry("Juby210", "Fox")
+ UserEntry("Vendicated", "the ven")
+ }
+ }
+ }
+
ContributorsCard(viewModel.contributors)
Spacer(Modifier.weight(1f, true))
@@ -58,11 +85,40 @@ fun AboutScreen(onClickBack: () -> Unit) {
) {
Icon(
painter = painterResource(R.drawable.ic_account_github_white_24dp),
- contentDescription = "GitHub"
+ contentDescription = stringResource(R.string.github)
)
- Spacer(Modifier.padding(4.dp))
+
+ Spacer(Modifier.width(ButtonDefaults.IconSpacing))
+
Text(stringResource(R.string.github))
}
}
}
}
+
+@Composable
+private fun UserEntry(name: String, roles: String) {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ AsyncImage(
+ modifier = Modifier
+ .size(54.dp)
+ .clip(CircleShape),
+ model = "https://github.com/$name.png",
+ contentDescription = name
+ )
+
+ Column {
+ Text(
+ text = name,
+ style = MaterialTheme.typography.titleLarge
+ )
+
+ Text(roles)
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt
index 7369678..201edbe 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt
@@ -1,42 +1,35 @@
package com.aliucord.manager.ui.screen
-import android.content.res.Configuration
import androidx.compose.animation.*
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.*
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.DpOffset
-import androidx.compose.ui.unit.dp
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
-import com.aliucord.manager.ui.navigation.AppDestination
import com.aliucord.manager.ui.navigation.HomeDestination
-import com.xinto.taxi.*
+import com.xinto.taxi.Taxi
+import com.xinto.taxi.rememberNavigator
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable
-fun MainRootScreen(navigator: BackstackNavigator) {
+fun MainRootScreen(
+ onClickInstall: () -> Unit,
+ onClickAbout: () -> Unit,
+ onClickSettings: () -> Unit
+) {
val mainRootNavigator = rememberNavigator(HomeDestination.HOME)
- val currentDestination = mainRootNavigator.currentDestination
- val orientation = LocalConfiguration.current.orientation
- var isMenuExpanded by remember { mutableStateOf(false) }
Scaffold(
- modifier = Modifier
- .fillMaxSize(),
topBar = {
MediumTopAppBar(
- title = {
- Text(stringResource(mainRootNavigator.currentDestination.label))
- },
+ title = { Text(stringResource(mainRootNavigator.currentDestination.label)) },
actions = {
val localUriHandler = LocalUriHandler.current
@@ -47,125 +40,64 @@ fun MainRootScreen(navigator: BackstackNavigator) {
) {
Icon(
painter = painterResource(R.drawable.ic_discord),
- contentDescription = stringResource(R.string.support_server),
- tint = MaterialTheme.colorScheme.onBackground
+ contentDescription = stringResource(R.string.support_server)
)
}
- Box(
- modifier = Modifier.wrapContentSize(Alignment.TopEnd)
- ) {
- IconButton(
- onClick = { isMenuExpanded = !isMenuExpanded }
- ) {
- Icon(
- imageVector = Icons.Default.MoreVert,
- contentDescription = stringResource(R.string.show_menu),
- tint = MaterialTheme.colorScheme.onBackground
- )
- }
+ IconButton(onClick = onClickAbout) {
+ Icon(
+ imageVector = Icons.Default.Info,
+ contentDescription = stringResource(R.string.about)
+ )
+ }
- DropdownMenu(
- offset = DpOffset(x = 12.dp, y = 0.dp),
- expanded = isMenuExpanded,
- onDismissRequest = { isMenuExpanded = false }
- ) {
- DropdownMenuItem(
- onClick = {
- isMenuExpanded = false
- navigator.push(AppDestination.About)
- },
- leadingIcon = {
- Icon(
- imageVector = Icons.Default.Info,
- contentDescription = "About"
- )
- },
- text = { Text(stringResource(R.string.about)) }
- )
-
- DropdownMenuItem(
- onClick = {
- isMenuExpanded = false
- navigator.push(AppDestination.Settings)
- },
- leadingIcon = {
- Icon(
- imageVector = Icons.Default.Settings,
- contentDescription = "Settings"
- )
- },
- text = { Text(stringResource(R.string.settings)) }
- )
- }
+ IconButton(onClick = onClickSettings) {
+ Icon(
+ imageVector = Icons.Default.Settings,
+ contentDescription = stringResource(R.string.settings)
+ )
}
}
)
},
bottomBar = {
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- NavigationBar {
+ val currentDestination = mainRootNavigator.currentDestination
- HomeDestination.values().forEach { destination ->
- NavigationBarItem(
- selected = currentDestination == destination,
- icon = {
- if (currentDestination == destination) Icon(
- destination.selectedIcon,
- stringResource(destination.label)
- ) else Icon(destination.unselectedIcon, stringResource(destination.label))
- },
- label = { Text(stringResource(destination.label)) },
- onClick = { mainRootNavigator.replace(destination) }
- )
- }
+ NavigationBar {
+ HomeDestination.values().forEach { destination ->
+ NavigationBarItem(
+ selected = currentDestination == destination,
+ icon = {
+ if (currentDestination == destination) {
+ Icon(
+ imageVector = destination.selectedIcon,
+ contentDescription = stringResource(destination.label)
+ )
+ } else {
+ Icon(
+ imageVector = destination.unselectedIcon,
+ contentDescription = stringResource(destination.label)
+ )
+ }
+ },
+ label = { Text(stringResource(destination.label)) },
+ onClick = { mainRootNavigator.replace(destination) }
+ )
}
}
}
) { paddingValues ->
- Row(
- modifier = Modifier
- .padding(paddingValues)
- .padding(horizontal = 12.dp, vertical = 14.dp)
- ) {
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- NavRail(
- currentDestination = currentDestination,
- onClickDestination = mainRootNavigator::replace
+ Taxi(
+ modifier = Modifier.padding(paddingValues),
+ navigator = mainRootNavigator,
+ transitionSpec = { fadeIn() with fadeOut() }
+ ) { destination ->
+ when (destination) {
+ HomeDestination.HOME -> HomeScreen(
+ onClickInstall = onClickInstall
)
- }
-
- Taxi(
- modifier = Modifier.weight(1f, true),
- navigator = mainRootNavigator,
- transitionSpec = { fadeIn() with fadeOut() }
- ) { destination ->
- when (destination) {
- HomeDestination.HOME -> HomeScreen(navigator = navigator)
- HomeDestination.PLUGINS -> PluginsScreen(navigator = navigator)
- }
+ HomeDestination.PLUGINS -> PluginsScreen()
}
}
-
- }
-}
-
-@Composable
-fun NavRail(
- currentDestination: HomeDestination,
- onClickDestination: (HomeDestination) -> Unit
-) = NavigationRail {
- HomeDestination.values().forEach { destination ->
- NavigationRailItem(
- selected = currentDestination == destination,
- icon = {
- if (currentDestination == destination) Icon(
- destination.selectedIcon,
- stringResource(destination.label)
- ) else Icon(destination.unselectedIcon, stringResource(destination.label))
- },
- label = { Text(stringResource(destination.label)) },
- onClick = { onClickDestination(destination) }
- )
}
}
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
index 9ccd3a6..edeb9e9 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt
@@ -12,63 +12,56 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
+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.LocalUriHandler
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.paging.*
+import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import com.aliucord.manager.R
-import com.aliucord.manager.model.github.Commit
-import com.aliucord.manager.preferences.Prefs
-import com.aliucord.manager.ui.component.installer.DownloadMethod
-import com.aliucord.manager.ui.component.installer.InstallerDialog
-import com.aliucord.manager.ui.navigation.AppDestination
+import com.aliucord.manager.ui.component.DownloadMethod
+import com.aliucord.manager.ui.component.InstallerDialog
import com.aliucord.manager.ui.viewmodel.HomeViewModel
-import com.aliucord.manager.util.Github
-import com.xinto.taxi.BackstackNavigator
+import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun HomeScreen(navigator: BackstackNavigator) {
- val viewModel: HomeViewModel = viewModel()
-
+fun HomeScreen(
+ viewModel: HomeViewModel = getViewModel(),
+ onClickInstall: () -> Unit
+) {
var showOptionsDialog by remember { mutableStateOf(false) }
if (showOptionsDialog) {
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri == null) return@rememberLauncherForActivityResult
- navigator.push(AppDestination.Install)
+ onClickInstall()
}
InstallerDialog(
onDismissRequest = { showOptionsDialog = false },
onConfirm = { method ->
- if (method == DownloadMethod.DOWNLOAD) {
- navigator.push(AppDestination.Install)
- } else {
- filePicker.launch(arrayOf("application/octet-stream"))
+ when (method) {
+ DownloadMethod.DOWNLOAD -> onClickInstall()
+ DownloadMethod.SELECT -> filePicker.launch(arrayOf("application/octet-stream"))
}
}
)
}
Column(
- verticalArrangement = Arrangement.spacedBy(12.dp)
+ modifier = Modifier.padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- ElevatedCard(
- modifier = Modifier.wrapContentHeight()
- ) {
+ ElevatedCard {
Column(
modifier = Modifier
.padding(16.dp)
@@ -77,52 +70,53 @@ fun HomeScreen(navigator: BackstackNavigator) {
) {
Column {
Text(
- "Aliucord${Prefs.packageName.get().let { if (it != "com.aliucord") " ($it)" else "" }}",
+ text = "Aliucord${viewModel.preferences.packageName.let { if (it != "com.aliucord") " ($it)" else "" }}",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
- Text(buildAnnotatedString {
- append("Supported version: ")
+ Text(
+ buildAnnotatedString {
+ append("Supported version: ")
- withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
- append(viewModel.supportedVersion)
+ withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
+ append(viewModel.supportedVersion)
+ }
+
+ append("\nInstalled version: ")
+
+ withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
+ append(viewModel.installedVersion)
+ }
}
-
- append("\nInstalled version: ")
-
- withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
- append(viewModel.installedVersion)
- }
- })
- }
-
- val (drawable, description) = when (viewModel.installedVersion) {
- "-" -> R.drawable.ic_download_24dp to R.string.install
- viewModel.supportedVersion -> R.drawable.ic_reinstall_24dp to R.string.reinstall
- else -> R.drawable.ic_update_24dp to R.string.update
+ )
}
Column(
- modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
+ val (icon, description) = when (viewModel.installedVersion) {
+ "-" -> Icons.Default.Download to R.string.install
+ viewModel.supportedVersion -> Icons.Default.Update to R.string.reinstall
+ else -> Icons.Default.Update to R.string.update
+ }
+
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(
modifier = Modifier.weight(1f, true),
onClick = {
- if (Prefs.devMode.get()) {
+ if (viewModel.preferences.devMode) {
showOptionsDialog = true
} else {
- navigator.push(AppDestination.Install)
+ onClickInstall()
}
}
) {
Icon(
modifier = Modifier.padding(8.dp),
- painter = painterResource(drawable),
+ imageVector = icon,
contentDescription = stringResource(description),
tint = MaterialTheme.colorScheme.onPrimary
)
@@ -132,9 +126,7 @@ fun HomeScreen(navigator: BackstackNavigator) {
}
if (viewModel.installedVersion != "-") {
-
Row(
- modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
CompositionLocalProvider(
@@ -142,14 +134,12 @@ fun HomeScreen(navigator: BackstackNavigator) {
) {
FilledTonalButton(
modifier = Modifier.weight(1f),
- onClick = {
- viewModel.uninstallAliucord()
- }
+ onClick = viewModel::uninstallAliucord
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = Icons.Default.Delete,
- contentDescription = "Uninstall"
+ contentDescription = stringResource(R.string.uninstall)
)
}
@@ -159,8 +149,8 @@ fun HomeScreen(navigator: BackstackNavigator) {
) {
Icon(
modifier = Modifier.padding(8.dp),
- painter = painterResource(R.drawable.ic_launch_24dp),
- contentDescription = "Launch"
+ imageVector = Icons.Default.Launch,
+ contentDescription = stringResource(R.string.launch)
)
}
}
@@ -169,89 +159,60 @@ fun HomeScreen(navigator: BackstackNavigator) {
}
}
}
- val pager = remember {
- Pager(
- PagingConfig(
- pageSize = 30,
- enablePlaceholders = true,
- maxSize = 200
- )
- ) {
- object : PagingSource() {
- override suspend fun load(params: LoadParams): LoadResult {
- val pageNumber = params.key ?: 0
-
- val response = Github.getCommits("page" to pageNumber.toString())
- val prevKey = if (pageNumber > 0) pageNumber - 1 else null
- val nextKey = if (response.isNotEmpty()) pageNumber + 1 else null
-
- return LoadResult.Page(
- data = response,
- prevKey = prevKey,
- nextKey = nextKey
- )
- }
-
- override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let {
- state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
- }
- }
- }
- }
ElevatedCard(
- Modifier
- .fillMaxWidth()
- .weight(1f)
+ modifier = Modifier.padding(bottom = 16.dp)
) {
Column(
- modifier = Modifier.padding(16.dp, 16.dp, 16.dp),
+ modifier = Modifier
+ .padding(16.dp)
+ .weight(1f, true)
+ .fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- Row(
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- text = stringResource(R.string.commits),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.primary
- )
+ val lazyPagingItems = viewModel.commits.collectAsLazyPagingItems()
+
+ Text(
+ text = stringResource(R.string.commits),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.primary
+ )
- Spacer(Modifier.weight(1f, true))
- }
- val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn(
- modifier = Modifier
- .fillMaxWidth(),
+ modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
- if (lazyPagingItems.loadState.refresh == LoadState.Loading) item {
- Text(
- text = stringResource(R.string.paging_initial_load),
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentWidth(Alignment.CenterHorizontally)
- )
+ if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
+ item {
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentWidth(Alignment.CenterHorizontally),
+ text = stringResource(R.string.paging_initial_load)
+ )
+ }
}
items(lazyPagingItems) { commitData ->
if (commitData == null) return@items
- val localUriHandler = LocalUriHandler.current
+ val uriHandler = LocalUriHandler.current
ListItem(
- modifier = Modifier.clickable { localUriHandler.openUri(commitData.htmlUrl) },
+ modifier = Modifier.clickable { uriHandler.openUri(commitData.htmlUrl) },
overlineText = { Text(commitData.sha.substring(0, 7)) },
headlineText = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author.name}") }
)
}
- if (lazyPagingItems.loadState.append == LoadState.Loading) item {
- CircularProgressIndicator(
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentWidth(Alignment.CenterHorizontally)
- )
+ if (lazyPagingItems.loadState.append == LoadState.Loading) {
+ item {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentWidth(Alignment.CenterHorizontally)
+ )
+ }
}
}
}
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
index 5acd937..c054876 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt
@@ -5,8 +5,6 @@
package com.aliucord.manager.ui.screen
-import android.os.Build
-import androidx.annotation.RequiresApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -16,25 +14,21 @@ import androidx.compose.material.icons.filled.NavigateBefore
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import com.aliucord.manager.R
import com.aliucord.manager.ui.viewmodel.InstallViewModel
+import org.koin.androidx.compose.getViewModel
-@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun InstallerScreen(
+ viewModel: InstallViewModel = getViewModel(),
onClickBack: () -> Unit
) {
- val viewModel: InstallViewModel = viewModel()
-
val navigateMain by viewModel.returnToHome.collectAsState(initial = false)
- if (navigateMain) onClickBack
-
- LaunchedEffect(Unit) {
- viewModel.startInstallation()
- }
+ if (navigateMain) onClickBack()
Scaffold(
topBar = {
@@ -44,7 +38,7 @@ fun InstallerScreen(
IconButton(onClick = onClickBack) {
Icon(
imageVector = Icons.Default.NavigateBefore,
- contentDescription = "Back"
+ contentDescription = stringResource(R.string.back)
)
}
}
@@ -62,6 +56,7 @@ fun InstallerScreen(
modifier = Modifier.fillParentMaxWidth()
)
}
+
item {
Text(
text = viewModel.log,
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
index 1ba8f1d..ad7115d 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt
@@ -14,50 +14,100 @@ 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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
import com.aliucord.manager.R
-import com.aliucord.manager.model.Plugin
import com.aliucord.manager.ui.component.plugins.Changelog
import com.aliucord.manager.ui.component.plugins.PluginCard
-import com.aliucord.manager.ui.navigation.AppDestination
-import com.xinto.taxi.BackstackNavigator
+import com.aliucord.manager.ui.viewmodel.PluginsViewModel
+import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun PluginsScreen(navigator: BackstackNavigator) {
+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.uninstall)
+ )
+ },
+ title = {
+ Text("${stringResource(R.string.uninstall)} ${plugin.manifest.name}")
+ },
+ text = {
+ Text(stringResource(R.string.delete_plugin_body))
+ },
+ confirmButton = {
+ Button(
+ onClick = { viewModel.uninstallPlugin(plugin) }
+ ) {
+ 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(),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- var plugins by remember { mutableStateOf(Plugin.loadAll()) }
- val focusManager = LocalFocusManager.current
- var search by remember { mutableStateOf("") }
+ val search = viewModel.search
+
+ if (viewModel.plugins.isNotEmpty()) {
+ val focusManager = LocalFocusManager.current
- if (plugins.isNotEmpty()) {
OutlinedTextField(
- maxLines = 1,
- value = search,
- onValueChange = { search = it },
- singleLine = true,
modifier = Modifier.fillMaxWidth(),
+ maxLines = 1,
+ value = viewModel.search,
+ onValueChange = viewModel::search,
+ singleLine = true,
shape = RoundedCornerShape(12.dp),
label = { Text(stringResource(R.string.search)) },
trailingIcon = {
- if (search.isNotBlank()) {
- IconButton(onClick = { search = "" }) {
+ if (viewModel.search.isNotBlank()) {
+ IconButton(onClick = viewModel::clearSearch) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.clear)
)
}
} else {
- Icon(Icons.Default.Search, stringResource(R.string.search_plugins))
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = stringResource(R.string.search_plugins)
+ )
}
},
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search),
@@ -67,59 +117,19 @@ fun PluginsScreen(navigator: BackstackNavigator) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- items(plugins.filter { plugin ->
- plugin.manifest.run {
- name.contains(search, true)
- || description.contains(search, true)
- || authors.any { a -> a.name.contains(search, true) }
- }
- }) { plugin ->
- var showDeleteDialog by remember { mutableStateOf(false) }
- var showChangelogDialog by remember { mutableStateOf(false) }
-
- if (showDeleteDialog) AlertDialog(
- onDismissRequest = { showDeleteDialog = false },
- icon = {
- Icon(
- imageVector = Icons.Default.Delete,
- contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name)
- )
- },
- title = { Text(stringResource(R.string.uninstall_plugin, plugin.manifest.name)) },
- text = { Text(stringResource(R.string.delete_plugin_body)) },
- confirmButton = {
- Button(
- onClick = {
- showDeleteDialog = false
- plugin.file.delete()
- plugins = plugins.filter { it != plugin }
- }
- ) {
- Text(stringResource(android.R.string.ok))
- }
- },
- dismissButton = {
- Button(
- onClick = { showDeleteDialog = false },
- colors = ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.secondaryContainer,
- contentColor = MaterialTheme.colorScheme.onSecondaryContainer
- )
- ) {
- Text(stringResource(android.R.string.cancel))
- }
+ items(
+ viewModel.plugins.filter { plugin ->
+ plugin.manifest.run {
+ name.contains(search, true)
+ || description.contains(search, true)
+ || authors.any { (name) -> name.contains(search, true) }
}
- )
-
- if (showChangelogDialog) Dialog(
- onDismissRequest = { showChangelogDialog = false },
- content = { Changelog(plugin, onDismiss = { showChangelogDialog = false }) }
- )
-
+ }
+ ) { plugin ->
PluginCard(
plugin = plugin,
- onDelete = { showDeleteDialog = true },
- onShowChangelog = { showChangelogDialog = true }
+ onClickDelete = { viewModel.showUninstallDialog(plugin) },
+ onClickShowChangelog = { viewModel.showChangelogDialog(plugin) }
)
}
}
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
index fbeb3d0..b44c30a 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt
@@ -5,28 +5,30 @@
package com.aliucord.manager.ui.screen
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.*
+import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateBefore
+import androidx.compose.material.icons.filled.Style
import androidx.compose.material3.*
import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.aliucord.manager.R
-import com.aliucord.manager.preferences.Prefs
-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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun SettingsScreen(onClickBack: () -> Unit) {
+fun SettingsScreen(
+ viewModel: SettingsViewModel = getViewModel(),
+ onClickBack: () -> Unit
+) {
Scaffold(
topBar = {
MediumTopAppBar(
@@ -49,23 +51,25 @@ fun SettingsScreen(onClickBack: () -> Unit) {
.verticalScroll(state = rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
- val viewModifier: SettingsViewModel = viewModel()
+ val preferences = viewModel.preferences
- var showThemeDialog by remember { mutableStateOf(false) }
-
- if (showThemeDialog) {
+ if (viewModel.showThemeDialog) {
ThemeDialog(
- onDismissRequest = { showThemeDialog = false },
- onConfirm = { Prefs.theme.set(it.ordinal) }
+ currentTheme = preferences.theme,
+ onDismissRequest = viewModel::hideThemeDialog,
+ onConfirm = viewModel::setTheme
)
}
- ThemeSetting(onClick = { showThemeDialog = true })
-
- PreferenceItem(
- title = { Text(stringResource(R.string.use_black)) },
- description = { Text(stringResource(R.string.use_black_description)) },
- preference = Prefs.useBlack
+ ListItem(
+ modifier = Modifier.clickable(onClick = viewModel::showThemeDialog),
+ headlineText = { Text(stringResource(R.string.theme)) },
+ supportingText = { Text(stringResource(R.string.theme_setting_description)) },
+ trailingContent = {
+ FilledTonalButton(onClick = viewModel::showThemeDialog) {
+ Text(preferences.theme.displayName)
+ }
+ }
)
Divider()
@@ -74,27 +78,28 @@ fun SettingsScreen(onClickBack: () -> Unit) {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
- value = Prefs.appName.get(),
- onValueChange = Prefs.appName::set,
+ value = preferences.appName,
+ onValueChange = viewModel::setAppName,
label = { Text(stringResource(R.string.app_name_setting)) },
placeholder = { Text("Aliucord") },
singleLine = true
)
- PreferenceItem(
+ SwitchSetting(
+ checked = preferences.replaceBg,
title = { Text(stringResource(R.string.replace_bg)) },
- preference = Prefs.replaceBg
+ onCheckedChange = { preferences.replaceBg = it }
)
- val devModeOn = Prefs.devMode.get()
-
- PreferenceItem(
- title = { Text(stringResource(R.string.developer_mode)) },
- preference = Prefs.devMode
+ SwitchSetting(
+ checked = preferences.devMode,
+ title = { Text(stringResource(R.string.advanced_options)) },
+ onCheckedChange = { preferences.devMode = it }
)
AnimatedVisibility(
- visible = devModeOn,
+ visible = preferences.devMode,
+ enter = expandVertically(),
exit = shrinkVertically()
) {
Column(
@@ -103,18 +108,29 @@ fun SettingsScreen(onClickBack: () -> Unit) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
- .padding(horizontal = 8.dp),
- value = Prefs.packageName.get(),
- onValueChange = Prefs.packageName::set,
+ .padding(horizontal = 10.dp),
+ value = preferences.packageName,
+ onValueChange = viewModel::setPackageName,
label = { Text(stringResource(R.string.package_name)) },
placeholder = { Text("com.aliucord") },
singleLine = true
)
- PreferenceItem(
+ OutlinedTextField(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 10.dp),
+ value = preferences.version,
+ onValueChange = viewModel::setVersion,
+ label = { Text(stringResource(R.string.version)) },
+ singleLine = true
+ )
+
+ SwitchSetting(
+ checked = preferences.debuggable,
title = { Text(stringResource(R.string.debuggable)) },
description = { Text(stringResource(R.string.debuggable_description)) },
- preference = Prefs.debuggable
+ onCheckedChange = { preferences.debuggable = it }
)
}
}
@@ -123,10 +139,88 @@ fun SettingsScreen(onClickBack: () -> Unit) {
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
- onClick = viewModifier::clearCacheDir
+ onClick = viewModel::clearCacheDir
) {
- Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center)
+ Text(
+ text = stringResource(R.string.clear_files_cache),
+ textAlign = TextAlign.Center
+ )
}
}
}
}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SwitchSetting(
+ checked: Boolean,
+ icon: @Composable (() -> Unit)? = null,
+ title: @Composable () -> Unit,
+ description: @Composable (() -> Unit)? = null,
+ onCheckedChange: (Boolean) -> Unit,
+) {
+ ListItem(
+ modifier = Modifier.clickable { onCheckedChange(!checked) },
+ leadingContent = icon,
+ headlineText = title,
+ supportingText = description,
+ trailingContent = {
+ Switch(
+ checked = checked,
+ onCheckedChange = onCheckedChange
+ )
+ }
+ )
+}
+
+@Composable
+fun ThemeDialog(
+ currentTheme: Theme,
+ onDismissRequest: () -> Unit,
+ onConfirm: (Theme) -> Unit
+) {
+ var selectedTheme by remember { mutableStateOf(currentTheme) }
+
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ icon = {
+ Icon(
+ imageVector = Icons.Default.Style,
+ contentDescription = stringResource(R.string.theme)
+ )
+ },
+ title = { Text(stringResource(R.string.theme)) },
+ text = {
+ Column {
+ Theme.values().forEach { theme ->
+ Row(
+ modifier = Modifier.clickable { selectedTheme = theme },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = theme.displayName,
+ 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.apply))
+ }
+ }
+ )
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt
deleted file mode 100644
index 0a03f59..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt
+++ /dev/null
@@ -1,89 +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.material.icons.Icons
-import androidx.compose.material.icons.filled.NavigateBefore
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.aliucord.manager.R
-import com.aliucord.manager.util.httpClient
-import io.ktor.client.call.*
-import io.ktor.client.request.*
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.serialization.Serializable
-
-@Serializable
-private data class Tree(
- val tree: List
-) {
- @Serializable
- data class File(
- val mode: String,
- val sha: String
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun StoreScreen(onClickBack: () -> Unit) {
- val plugins = remember { mutableStateListOf() }
- // TODO: Display cards with option to install plugin
- Scaffold(
- topBar = {
- MediumTopAppBar(
- title = { Text(stringResource(R.string.store)) },
- navigationIcon = {
- IconButton(onClick = onClickBack) {
- Icon(
- imageVector = Icons.Default.NavigateBefore,
- contentDescription = stringResource(R.string.back)
- )
- }
- }
- )
- }
- ) { paddingValues ->
- Column(
- modifier = Modifier
- .padding(paddingValues)
- ) {
- if (plugins.isEmpty()) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .padding(12.dp),
- contentAlignment = Alignment.Center,
- content = { CircularProgressIndicator() }
- )
-
- LaunchedEffect(Unit) {
- launch(Dispatchers.IO) {
- val content = httpClient.get("https://api.github.com/repos/DiamondMiner88/AllACPlugins/git/trees/master") {
- header("accept", "application/json")
- }.body()
-
- plugins.addAll(content.tree.filter { it.mode == "160000" })
- }
- }
- } else {
- LazyColumn {
- items(plugins) { item ->
- Text("SHA: ${item.sha}", style = MaterialTheme.typography.labelSmall)
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Color.kt b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Color.kt
deleted file mode 100644
index a5d9ecb..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Color.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-
-package com.aliucord.manager.ui.theme
-
-import androidx.compose.ui.graphics.Color
-
-val darkBackground = Color(0xff1a1a1a)
-
-val primaryColor = Color(0xff00c853)
-val primaryColorLight = Color(0xff5efc82)
-val primaryColorDark = Color(0xff009624)
-
-val discordBrand = Color(0xff5865F2)
-val discordGreen = Color(0xff57F287)
-val discordRed = Color(0xffED4245)
-
-val errorColor = discordRed
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt
index 2961cc7..020a6db 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt
@@ -10,27 +10,11 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.systemuicontroller.rememberSystemUiController
-private val LightColorScheme = lightColorScheme()
-private val DarkColorScheme = darkColorScheme()
-private val BlackColorScheme = darkColorScheme(
- background = Color.Black,
- surface = Color.Black,
- primary = Color.LightGray,
- onPrimary = Color.DarkGray,
- secondary = Color.Gray,
- onSecondary = Color.LightGray,
- secondaryContainer = Color.DarkGray,
- onSecondaryContainer = Color.White,
- outline = Color.LightGray
-)
-
@Composable
fun ManagerTheme(
- isBlack: Boolean = false,
isDarkTheme: Boolean = isSystemInDarkTheme(),
isDynamicColor: Boolean = true,
content: @Composable () -> Unit
@@ -39,8 +23,8 @@ fun ManagerTheme(
val colorScheme = when {
dynamicColor && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current)
- isDarkTheme -> if (isBlack) BlackColorScheme else DarkColorScheme
- else -> LightColorScheme
+ isDarkTheme -> darkColorScheme()
+ else -> lightColorScheme()
}
val systemUiController = rememberSystemUiController()
@@ -54,19 +38,12 @@ fun ManagerTheme(
MaterialTheme(
colorScheme = colorScheme,
- typography = Typography,
content = content
)
}
-enum class Theme(
- val displayName: String
-) {
+enum class Theme(val displayName: String) {
SYSTEM("System"),
LIGHT("Light"),
- DARK("Dark");
-
- companion object {
- fun from(index: Int) = values()[index]
- }
+ DARK("Dark")
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Type.kt b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Type.kt
deleted file mode 100644
index 5788f38..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Type.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-
-package com.aliucord.manager.ui.theme
-
-import androidx.compose.material3.Typography
-
-val Typography = Typography()
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt
index 37a8c6b..a75241f 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt
@@ -3,29 +3,18 @@ package com.aliucord.manager.ui.viewmodel
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.aliucord.manager.model.github.GithubUser
-import com.aliucord.manager.util.Github
-import com.aliucord.manager.util.httpClient
-import io.ktor.client.call.*
-import io.ktor.client.request.*
+import com.aliucord.manager.network.dto.GithubUser
+import com.aliucord.manager.domain.repository.GithubRepository
import kotlinx.coroutines.launch
-class AboutViewModel : ViewModel() {
-
+class AboutViewModel(
+ private val githubRepository: GithubRepository
+) : ViewModel() {
val contributors = mutableStateListOf()
- fun load() {
+ init {
viewModelScope.launch {
- val githubContributors = httpClient.get(Github.contributorsUrl).body>()
- .sortedByDescending {
- it.contributions
- }
- contributors.addAll(githubContributors)
+ contributors.addAll(githubRepository.getContributors())
}
}
-
- init {
- load()
- }
-
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt
index 693f267..8f4938d 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/HomeViewModel.kt
@@ -5,18 +5,21 @@ import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.runtime.*
-import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.aliucord.manager.model.github.Version
-import com.aliucord.manager.preferences.Prefs
-import com.aliucord.manager.util.*
-import io.ktor.client.request.*
-import io.ktor.client.statement.*
-import kotlinx.coroutines.*
-import kotlinx.serialization.decodeFromString
+import androidx.paging.*
+import com.aliucord.manager.domain.manager.PreferencesManager
+import com.aliucord.manager.domain.repository.GithubRepository
+import com.aliucord.manager.network.dto.Commit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
-class HomeViewModel(application: Application) : AndroidViewModel(application) {
- private val packageManager = getApplication().packageManager
+class HomeViewModel(
+ private val application: Application,
+ private val githubRepository: GithubRepository,
+ val preferences: PreferencesManager
+) : ViewModel() {
+ private val packageManager = application.packageManager
var supportedVersion by mutableStateOf("")
private set
@@ -24,43 +27,64 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
var installedVersion by mutableStateOf("-")
private set
- private fun load() {
- viewModelScope.launch(Dispatchers.IO) {
- val version = json.decodeFromString(httpClient.get(Github.dataUrl).bodyAsText())
+ val commits = Pager(PagingConfig(pageSize = 30)) {
+ object : PagingSource() {
+ override suspend fun load(params: LoadParams): LoadResult {
+ val page = params.key ?: 0
- withContext(Dispatchers.Main) {
- supportedVersion = "${version.versionName} - " + when (version.versionCode[3]) {
- '0' -> "Stable"
- '1' -> "Beta"
- '2' -> "Alpha"
- else -> throw NoWhenBranchMatchedException()
- }
- installedVersion = try {
- packageManager.getPackageInfo(Prefs.packageName.get(), 0).versionName
- } catch (th: Throwable) {
- "-"
- }
+ val response = githubRepository.getCommits(page)
+ val prevKey = if (page > 0) page - 1 else null
+ val nextKey = if (response.isNotEmpty()) page + 1 else null
+
+ return LoadResult.Page(
+ data = response,
+ prevKey = prevKey,
+ nextKey = nextKey
+ )
+ }
+
+ override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let {
+ state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
+ }
+ }
+ }.flow.cachedIn(viewModelScope)
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ val version = githubRepository.getVersion()
+
+ supportedVersion = "${version.versionName} - " + when (version.versionCode[3]) {
+ '0' -> "Stable"
+ '1' -> "Beta"
+ '2' -> "Alpha"
+ else -> throw NoWhenBranchMatchedException()
+ }
+
+ installedVersion = try {
+ @Suppress("DEPRECATION")
+ packageManager.getPackageInfo(preferences.packageName, 0).versionName
+ } catch (th: Throwable) {
+ "-"
}
}
}
fun launchAliucord() {
- packageManager.getLaunchIntentForPackage(Prefs.packageName.get())?.let {
- getApplication().startActivity(it)
- } ?: Toast.makeText(getApplication(), "Failed to launch Aliucord", Toast.LENGTH_LONG)
- .show()
+ val launchIntent = packageManager.getLaunchIntentForPackage(preferences.packageName)
+
+ if (launchIntent != null) {
+ application.startActivity(launchIntent)
+ } else {
+ Toast.makeText(application, "Failed to launch Aliucord", Toast.LENGTH_LONG).show()
+ }
}
fun uninstallAliucord() {
- val packageURI = Uri.parse("package:${Prefs.packageName.get()}")
+ val packageURI = Uri.parse("package:${preferences.packageName}")
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
- getApplication().startActivity(uninstallIntent)
- }
-
- init {
- load()
+ application.startActivity(uninstallIntent)
}
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt
index e1994d2..2ba1090 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt
@@ -8,21 +8,29 @@ import android.content.pm.PackageInstaller
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.*
-import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.aliucord.manager.domain.manager.DownloadManager
+import com.aliucord.manager.domain.manager.PreferencesManager
import com.aliucord.manager.installer.service.InstallService
-import com.aliucord.manager.installer.util.DownloadUtils
import com.aliucord.manager.installer.util.ManifestPatcher
-import com.aliucord.manager.util.Signer
+import com.aliucord.manager.installer.util.Signer
import com.android.zipflinger.*
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.launch
import java.io.File
import java.util.zip.Deflater
-class InstallViewModel(application: Application) : AndroidViewModel(application) {
+class InstallViewModel(
+ private val application: Application,
+ private val downloadManager: DownloadManager,
+ private val preferences: PreferencesManager
+) : ViewModel() {
+ private val assetManager = application.assets
+ private val externalCacheDir = application.externalCacheDir!!
+ private val packageInstaller = application.packageManager.packageInstaller
+
private val _returnToHome = MutableSharedFlow()
val returnToHome = _returnToHome.asSharedFlow()
@@ -31,39 +39,39 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
var log by mutableStateOf("")
private set
+ init {
+ viewModelScope.launch {
+ startInstallation()
+ }
+ }
+
@RequiresApi(Build.VERSION_CODES.O)
- fun startInstallation() {
+ suspend fun startInstallation() {
if (installationRunning) return
- viewModelScope.launch(Dispatchers.IO) {
+ withContext(Dispatchers.IO) {
installationRunning = true
- val context = getApplication().applicationContext
- val assetManager = context.assets
- val externalCacheDir = context.externalCacheDir!!
val arch = Build.SUPPORTED_ABIS.first()
- val supportedVersion = "124206"
+ val supportedVersion = preferences.version
val baseApk = externalCacheDir.resolve("base-${supportedVersion}.apk").also { file ->
- val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0)
+ @Suppress("DEPRECATION")
+ val archiveInfo = application.packageManager.getPackageArchiveInfo(file.path, 0)
- if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also run { log += "Using cached base APK\n"}
+ if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also run { log += "Using cached base APK\n" }
log += "Downloading Discord APK... "
-
- DownloadUtils.downloadDiscordApk(context, supportedVersion)
-
+ downloadManager.downloadDiscordApk(supportedVersion)
log += "Done\n"
}
val libArch = arch.replace("-v", "_v")
val libsApk = externalCacheDir.resolve("config.$libArch-${supportedVersion}.apk").also { file ->
- if (file.exists()) return@also run { log += "Used cached libs APK\n" }
+ if (file.exists()) return@also run { log += "Using cached libs APK\n" }
log += "Downloading libs APK... "
-
- DownloadUtils.downloadSplit(context, supportedVersion, "config.$libArch")
-
+ downloadManager.downloadSplit(supportedVersion, "config.$libArch")
log += "Done\n"
}
@@ -71,23 +79,19 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
if (file.exists()) return@also run { log += "Using cached locale APK\n" }
log += "Downloading locale APK... "
-
- DownloadUtils.downloadSplit(context, supportedVersion, "config.en")
-
+ downloadManager.downloadSplit(supportedVersion, "config.en")
log += "Done\n"
}
val xxhdpiApk = externalCacheDir.resolve("config.xxhdpi-${supportedVersion}.apk").also { file ->
- if (file.exists()) return@also run { log += "Using cached drawables APK\n"}
+ if (file.exists()) return@also run { log += "Using cached drawables APK\n" }
log += "Downloading drawables APK... "
-
- DownloadUtils.downloadSplit(context, supportedVersion, "config.xxhdpi")
-
+ downloadManager.downloadSplit(supportedVersion, "config.xxhdpi")
log += "Done\n"
}
- val apks = listOf(baseApk, libsApk, localeApk, xxhdpiApk)
+ val apks = arrayOf(baseApk, libsApk, localeApk, xxhdpiApk)
log += "Patching manifest...\n"
@@ -97,7 +101,14 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
.array()
ZipArchive(baseApk.toPath()).use { zip ->
- val source = BytesSource(ManifestPatcher.patchManifest(byteArray), "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION)
+ val patchedBytesArray = ManifestPatcher.patchManifest(
+ manifestBytes = byteArray,
+ packageName = preferences.packageName,
+ appName = preferences.appName,
+ debuggable = preferences.debuggable,
+ )
+
+ val source = BytesSource(patchedBytesArray, "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION)
zip.delete("AndroidManifest.xml")
zip.add(source)
@@ -109,15 +120,18 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
.array()
ZipArchive(apk.toPath()).use { zip ->
- val source = BytesSource(ManifestPatcher.renamePackage(byteArray), "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION)
+ val patchedBytes = ManifestPatcher.renamePackage(byteArray, preferences.packageName)
+ val source = BytesSource(patchedBytes, "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION)
zip.delete("AndroidManifest.xml")
zip.add(source)
}
}
} catch (e: Exception) {
- log += "An error has occurred"
+ log += "An error has occurred: ${e.message}\n"
e.printStackTrace()
+
+ return@withContext
}
log += "Patching libraries...\n"
@@ -141,7 +155,7 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
log += "Done\n"
- installApks(*apks.toTypedArray())
+ installApks(*apks)
_returnToHome.emit(true)
installationRunning = false
@@ -149,9 +163,7 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
}
private fun installApks(vararg apks: File) {
- val context = getApplication().applicationContext
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
- val packageInstaller = context.packageManager.packageInstaller
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
@@ -163,13 +175,13 @@ class InstallViewModel(application: Application) : AndroidViewModel(application)
}
}
- val callbackIntent = Intent(context, InstallService::class.java)
+ val callbackIntent = Intent(application, InstallService::class.java)
@SuppressLint("UnspecifiedImmutableFlag")
val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- PendingIntent.getService(context, 0, callbackIntent, PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getService(application, 0, callbackIntent, PendingIntent.FLAG_MUTABLE)
} else {
- PendingIntent.getService(context, 0, callbackIntent, 0)
+ PendingIntent.getService(application, 0, callbackIntent, 0)
}
session.commit(contentIntent.intentSender)
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt
new file mode 100644
index 0000000..eda6f9a
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/PluginsViewModel.kt
@@ -0,0 +1,85 @@
+package com.aliucord.manager.ui.viewmodel
+
+import android.util.Log
+import androidx.compose.runtime.*
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.aliucord.manager.BuildConfig
+import com.aliucord.manager.aliucordDir
+import com.aliucord.manager.domain.model.Plugin
+import kotlinx.coroutines.*
+
+class PluginsViewModel : ViewModel() {
+ var showChangelogDialog by mutableStateOf(false)
+ private set
+
+ var showUninstallDialog by mutableStateOf(false)
+ private set
+
+ var search by mutableStateOf("")
+ private set
+
+ var selectedPlugin by mutableStateOf(null)
+ private set
+
+ val plugins = mutableStateListOf()
+
+ init {
+ viewModelScope.launch {
+ loadPlugins()
+ }
+ }
+
+ fun search(search: String) {
+ this.search = search
+ }
+
+ fun clearSearch() {
+ search = ""
+ }
+
+ fun showChangelogDialog(plugin: Plugin) {
+ showChangelogDialog = true
+ selectedPlugin = plugin
+ }
+
+ fun hideChangelogDialog() {
+ showChangelogDialog = false
+ }
+
+ fun showUninstallDialog(plugin: Plugin) {
+ showUninstallDialog = true
+ selectedPlugin = plugin
+ }
+
+ fun hideUninstallDialog() {
+ showUninstallDialog = false
+ }
+
+ fun uninstallPlugin(plugin: Plugin) {
+ plugin.file.delete()
+ plugins.remove(plugin)
+ }
+
+ private suspend fun loadPlugins(): Unit = withContext(Dispatchers.IO) {
+ val pluginsDir = aliucordDir.resolve("plugins")
+
+ if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
+ Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?")
+ return@withContext
+ }
+
+ val files = pluginsDir.listFiles { file -> file.extension == "zip" } ?: return@withContext
+
+ plugins.addAll(
+ files.mapNotNull { file ->
+ try {
+ Plugin(file)
+ } catch (e: Exception) {
+ Log.e(BuildConfig.TAG, "Failed to load plugin ${file.nameWithoutExtension}", e)
+ null
+ }
+ }.sortedBy { it.manifest.name }
+ )
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt
index 5d1c2ba..accb3ca 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/SettingsViewModel.kt
@@ -1,12 +1,43 @@
package com.aliucord.manager.ui.viewmodel
import android.app.Application
-import androidx.lifecycle.AndroidViewModel
+import androidx.compose.runtime.*
+import androidx.lifecycle.ViewModel
+import com.aliucord.manager.domain.manager.PreferencesManager
+import com.aliucord.manager.ui.theme.Theme
-class SettingsViewModel(application: Application) : AndroidViewModel(application) {
+class SettingsViewModel(
+ private val application: Application,
+ val preferences: PreferencesManager
+) : ViewModel() {
+ var showThemeDialog by mutableStateOf(false)
+ private set
- fun clearCacheDir() {
- getApplication().cacheDir.deleteRecursively()
+ fun showThemeDialog() {
+ showThemeDialog = true
}
+ fun hideThemeDialog() {
+ showThemeDialog = false
+ }
+
+ fun setPackageName(packageName: String) {
+ if (packageName.isNotBlank()) preferences.packageName = packageName
+ }
+
+ fun setAppName(appName: String) {
+ if (appName.isNotBlank()) preferences.appName = appName
+ }
+
+ fun setTheme(theme: Theme) {
+ preferences.theme = theme
+ }
+
+ fun setVersion(version: String) {
+ if (version.isNotBlank()) preferences.version = version
+ }
+
+ fun clearCacheDir() {
+ application.cacheDir.deleteRecursively()
+ }
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Const.kt b/app/src/main/kotlin/com/aliucord/manager/util/Const.kt
deleted file mode 100644
index 65e2d19..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/util/Const.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.util
-
-import android.os.Environment
-import io.ktor.client.*
-import io.ktor.client.plugins.contentnegotiation.*
-import io.ktor.serialization.kotlinx.json.*
-import kotlinx.serialization.json.Json
-
-val json = Json {
- prettyPrint = true
- ignoreUnknownKeys = true
-}
-
-val httpClient = HttpClient {
- install(ContentNegotiation) {
- json(json)
- }
-}
-val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Github.kt b/app/src/main/kotlin/com/aliucord/manager/util/Github.kt
deleted file mode 100644
index 5fef249..0000000
--- a/app/src/main/kotlin/com/aliucord/manager/util/Github.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2022 Juby210 & zt
- * Licensed under the Open Software License version 3.0
- */
-
-package com.aliucord.manager.util
-
-import com.aliucord.manager.model.github.Commit
-import com.aliucord.manager.model.github.Version
-import io.ktor.client.call.*
-import io.ktor.client.request.*
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.json.decodeFromStream
-import java.net.URL
-import kotlin.collections.set
-
-object Github {
- private const val org = "Aliucord"
- private const val repo = "Aliucord"
-
- private const val commitsUrl = "https://api.github.com/repos/$org/$repo/commits"
- const val contributorsUrl = "https://api.github.com/repos/$org/$repo/contributors"
- const val dataUrl = "https://raw.githubusercontent.com/$org/$repo/builds/data.json"
-
- fun checkForUpdates() {
- // TODO
- }
-
- @OptIn(ExperimentalSerializationApi::class)
- val version: Version by lazy {
- json.decodeFromStream(URL(dataUrl).openStream())
- }
-
- private val commitsCache = HashMap>()
-
- suspend fun getCommits(vararg params: Pair): List {
- val query = params.joinToString("&", "?") { it.first + "=" + it.second }
-
- if (commitsCache.containsKey(query)) return commitsCache[query] ?: emptyList()
-
- val res = httpClient.get(commitsUrl) {
- params.forEach { (key, value) -> parameter(key, value) }
- }.body>()
-
- commitsCache[query] = res
-
- return res
- }
-}
diff --git a/app/src/main/res/drawable/ic_account_github_white_24dp.xml b/app/src/main/res/drawable/ic_account_github_white_24dp.xml
index bb914a0..2d4f0b4 100644
--- a/app/src/main/res/drawable/ic_account_github_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_account_github_white_24dp.xml
@@ -1,11 +1,11 @@
+ android:fillType="evenOdd"
+ android:pathData="M12.0001,2C9.6237,1.9976 7.3241,2.8415 5.5135,4.3806C3.7028,5.9196 2.4994,8.0532 2.1189,10.3989C1.7384,12.7446 2.2057,15.1491 3.4371,17.1816C4.6684,19.2141 6.5833,20.7416 8.8386,21.4905C9.342,21.5826 9.52,21.2695 9.52,21.0055V19.3051C6.7269,19.9128 6.1376,17.9607 6.1376,17.9607C5.9523,17.3557 5.5586,16.8359 5.0265,16.4936C4.1241,15.8797 5.1001,15.8797 5.1001,15.8797C5.4164,15.9244 5.7185,16.0404 5.9833,16.2191C6.2481,16.3978 6.4688,16.6344 6.6287,16.911C6.7645,17.1573 6.9476,17.3743 7.1675,17.5497C7.3874,17.725 7.6398,17.8552 7.9102,17.9328C8.1805,18.0103 8.4635,18.0337 8.7429,18.0017C9.0223,17.9696 9.2927,17.8827 9.5384,17.7459C9.577,17.2409 9.7947,16.7662 10.1523,16.4076C7.9301,16.1559 5.5974,15.2965 5.5974,11.4966C5.5821,10.504 5.9492,9.5435 6.6225,8.814C6.3226,7.9509 6.3577,7.0067 6.7207,6.1682C6.7207,6.1682 7.5617,5.8981 9.4709,7.1934C11.1103,6.7433 12.8407,6.7433 14.4801,7.1934C16.3892,5.8981 17.2241,6.1682 17.2241,6.1682C17.5922,6.9978 17.6361,7.9353 17.3469,8.7956C18.0203,9.5251 18.3873,10.4856 18.3721,11.4782C18.3721,15.3211 16.0332,16.1621 13.8048,16.3892C14.0438,16.6294 14.2283,16.9181 14.3459,17.2359C14.4634,17.5536 14.5113,17.8929 14.4862,18.2308V20.9748C14.4862,21.3002 14.6643,21.5519 15.1738,21.4537C17.4034,20.685 19.2896,19.1531 20.4992,17.1286C21.7088,15.104 22.164,12.7171 21.7842,10.3895C21.4045,8.0619 20.2143,5.9434 18.424,4.4082C16.6336,2.8731 14.3583,2.0201 12.0001,2Z" />
diff --git a/app/src/main/res/drawable/ic_download_24dp.xml b/app/src/main/res/drawable/ic_download_24dp.xml
deleted file mode 100644
index 103827b..0000000
--- a/app/src/main/res/drawable/ic_download_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_history_white_24dp.xml b/app/src/main/res/drawable/ic_history_white_24dp.xml
deleted file mode 100644
index f3e3f07..0000000
--- a/app/src/main/res/drawable/ic_history_white_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_launch_24dp.xml b/app/src/main/res/drawable/ic_launch_24dp.xml
deleted file mode 100644
index 3fcd087..0000000
--- a/app/src/main/res/drawable/ic_launch_24dp.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_plugin_24dp.xml b/app/src/main/res/drawable/ic_plugin_24dp.xml
deleted file mode 100644
index 986ff93..0000000
--- a/app/src/main/res/drawable/ic_plugin_24dp.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_reinstall_24dp.xml b/app/src/main/res/drawable/ic_reinstall_24dp.xml
deleted file mode 100644
index f986635..0000000
--- a/app/src/main/res/drawable/ic_reinstall_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_update_24dp.xml b/app/src/main/res/drawable/ic_update_24dp.xml
deleted file mode 100644
index d51c785..0000000
--- a/app/src/main/res/drawable/ic_update_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 50ee3b4..40fe31e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,16 +6,15 @@
Open settings
You need to grant storage permission to use this app
Grant permission
- Replace app icon with Aliucord logo
+ Replace app icon with Aliucord variant
Settings
Show menu
Theme
Use Injector.dex from storage
Plugins
- Uninstall %1$s
Search
View changelog for %1$s
- Are you sure you want to uninstall this plugin? This action cannot be undone.
+ Are you sure you want to uninstall this plugin?
Close
Support Server
Back
@@ -23,7 +22,7 @@
Install
Reinstall
Update
- Developer Mode
+ Show advanced options
Debuggable
Enable debuggable flag
About
@@ -32,11 +31,8 @@
Search Plugins
GitHub
Installer
- Use black
- Use black colors for dark mode
Manager appearance
No plugins installed!
- Store
Loading…
Contributor
Apply
@@ -49,4 +45,7 @@
App Name
Package Name
Clear
+ Launch
+ Uninstall
+ Version
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1a7c308..176dc58 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Jul 04 12:13:32 EDT 2022
+#Sun Aug 14 21:28:22 EDT 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME