Fixed installer
This commit is contained in:
parent
5e1a71d4d4
commit
739196fa52
41 changed files with 747 additions and 616 deletions
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
val pagingVersion = "3.1.0"
|
||||
val accompanistVersion = "0.23.0"
|
||||
val composeVersion = "1.1.0"
|
||||
val ktorVersion = "1.6.7"
|
||||
|
|
@ -10,6 +11,7 @@ val ktorVersion = "1.6.7"
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("com.google.devtools.ksp") version "1.6.10-1.0.4"
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -49,32 +51,47 @@ android {
|
|||
composeOptions.kotlinCompilerExtensionVersion = composeVersion
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
debug {
|
||||
kotlin.srcDir("build/generated/ksp/debug/kotlin")
|
||||
}
|
||||
release {
|
||||
kotlin.srcDir("build/generated/ksp/release/kotlin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree("./libs"))
|
||||
// core dependencies
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("com.github.aliucord:libzip:1.0.1")
|
||||
// implementation("com.github.aliucord:libzip:1.0.1")
|
||||
implementation("androidx.core:core-splashscreen:1.0.0-beta01")
|
||||
|
||||
// compose dependencies
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
implementation("androidx.compose.ui:ui:${composeVersion}")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-alpha06")
|
||||
implementation("androidx.compose.ui:ui-tooling:${composeVersion}")
|
||||
implementation("androidx.compose.material:material:${composeVersion}")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview:${composeVersion}")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.1")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-alpha06")
|
||||
implementation("androidx.paging:paging-compose:1.0.0-alpha14")
|
||||
|
||||
// accompanist dependencies
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}")
|
||||
implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}")
|
||||
|
||||
// compose destinations
|
||||
implementation("io.github.raamcosta.compose-destinations:core:1.3.2-beta")
|
||||
ksp("io.github.raamcosta.compose-destinations:ksp:1.3.2-beta")
|
||||
|
||||
// other dependencies
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
implementation("io.coil-kt:coil-compose:1.4.0")
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.65")
|
||||
|
||||
// ktor
|
||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-gson:$ktorVersion")
|
||||
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.65")
|
||||
}
|
||||
BIN
app/libs/libzip-release.aar
Normal file
BIN
app/libs/libzip-release.aar
Normal file
Binary file not shown.
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
|
|
@ -17,4 +17,5 @@
|
|||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class com.aliucord.manager.models.** { private final *; }
|
||||
-keep class com.aliucord.manager.models.** { private final *; }
|
||||
-keep class com.aliucord.libzip.Zip { private final *; }
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AliucordManager"
|
||||
android:theme="@style/Theme.AliucordManager.SplashScreen"
|
||||
tools:ignore="AllowBackup">
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.aliucord.manager
|
||||
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey)
|
||||
|
|
@ -5,111 +5,67 @@
|
|||
|
||||
package com.aliucord.manager
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.aliucord.manager.preferences.Preferences
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import com.aliucord.manager.preferences.Prefs
|
||||
import com.aliucord.manager.preferences.sharedPreferences
|
||||
import com.aliucord.manager.preferences.themePref
|
||||
import com.aliucord.manager.ui.Screen
|
||||
import com.aliucord.manager.ui.components.AppBar
|
||||
import com.aliucord.manager.ui.components.GrantPermission
|
||||
import com.aliucord.manager.ui.screens.*
|
||||
import com.aliucord.manager.ui.components.ManagerScaffold
|
||||
import com.aliucord.manager.ui.theme.ManagerTheme
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import com.aliucord.manager.utils.Github
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionRequired
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@ExperimentalFoundationApi
|
||||
class MainActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
sharedPreferences = getPreferences(Context.MODE_PRIVATE)
|
||||
installSplashScreen().setOnExitAnimationListener { provider ->
|
||||
provider.view.animate().apply {
|
||||
interpolator = AccelerateInterpolator()
|
||||
duration = 200L
|
||||
|
||||
Github.checkForUpdates()
|
||||
alpha(0f)
|
||||
withEndAction(provider::remove)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
sharedPreferences = getPreferences(Context.MODE_PRIVATE)
|
||||
|
||||
setContent {
|
||||
ManagerTheme(
|
||||
isBlack = Preferences.useBlack.get(),
|
||||
isBlack = Prefs.useBlack.get(),
|
||||
isDarkTheme = run {
|
||||
val theme = Theme.from(themePref.get())
|
||||
val theme = Theme.from(Prefs.theme.get())
|
||||
|
||||
theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK
|
||||
}
|
||||
) {
|
||||
MainActivityLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@ExperimentalMaterial3Api
|
||||
@ExperimentalFoundationApi
|
||||
@ExperimentalPermissionsApi
|
||||
@Composable
|
||||
private fun MainActivityLayout() {
|
||||
val storagePermissionState = rememberPermissionState(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
val navController = rememberNavController()
|
||||
val requestAllFilesAccess = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
val context = LocalContext.current
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||
SideEffect {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
.setData("package:com.aliucord.manager".toUri())
|
||||
|
||||
SideEffect {
|
||||
@SuppressLint("NewApi")
|
||||
if (requestAllFilesAccess && !Environment.isExternalStorageManager()) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
.setData("package:com.aliucord.manager".toUri())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
} else if (storagePermissionState.shouldShowRationale) storagePermissionState.launchPermissionRequest()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { AppBar(navController) }
|
||||
) { paddingValues ->
|
||||
PermissionRequired(
|
||||
permissionState = storagePermissionState,
|
||||
permissionNotAvailableContent = { GrantPermission(storagePermissionState) },
|
||||
permissionNotGrantedContent = { GrantPermission(storagePermissionState) }
|
||||
) {
|
||||
NavHost(
|
||||
navController,
|
||||
modifier = Modifier.padding(paddingValues).padding(12.dp),
|
||||
startDestination = Screen.Home.route
|
||||
) {
|
||||
composable(Screen.Home.route) { HomeScreen(navController) }
|
||||
composable(Screen.Commits.route) { CommitsScreen() }
|
||||
composable(Screen.About.route) { AboutScreen() }
|
||||
composable(Screen.Settings.route) { SettingsScreen() }
|
||||
composable(Screen.Store.route) { StoreScreen() }
|
||||
composable(Screen.Installer.route) { InstallerScreen(navController) }
|
||||
ManagerScaffold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
package com.aliucord.manager.utils
|
||||
package com.aliucord.manager.models
|
||||
|
||||
import android.util.Log
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.models.Manifest
|
||||
import com.aliucord.manager.utils.gson
|
||||
import com.aliucord.manager.utils.pluginsDir
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.ZipException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class Plugin(val file: File) {
|
||||
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.")
|
||||
zipFile.getInputStream(entry).use { zis ->
|
||||
manifest = gson.fromJson(InputStreamReader(zis), Manifest::class.java)
|
||||
|
||||
manifest = zipFile.getInputStream(entry).use { zis ->
|
||||
gson.fromJson(zis.reader(), Manifest::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,14 +32,16 @@ class Plugin(val file: File) {
|
|||
val files = pluginsDir.listFiles() ?: return emptyList()
|
||||
|
||||
return files.mapNotNull { file ->
|
||||
if (!file.name.endsWith(".zip")) {
|
||||
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
|
||||
} else try {
|
||||
Plugin(file)
|
||||
} catch (th: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to load plugin ${file.nameWithoutExtension}", th)
|
||||
null
|
||||
}
|
||||
}.sortedBy { it.manifest.name }
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.models
|
||||
package com.aliucord.manager.models.github
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.models
|
||||
package com.aliucord.manager.models.github
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.aliucord.manager.models
|
||||
package com.aliucord.manager.models.github
|
||||
|
||||
data class Version(
|
||||
val versionCode: String,
|
||||
|
|
@ -10,7 +10,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.core.content.edit
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
var sharedPreferences: SharedPreferences? = null
|
||||
lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
class Preference<T>(
|
||||
private val key: String,
|
||||
|
|
@ -18,7 +18,7 @@ class Preference<T>(
|
|||
getter: SharedPreferences.(key: String, defaultValue: T) -> T?,
|
||||
private val setter: SharedPreferences.Editor.(key: String, newValue: T) -> Unit
|
||||
) {
|
||||
val value = mutableStateOf(sharedPreferences!!.getter(key, defaultValue) ?: defaultValue)
|
||||
private val value = mutableStateOf(sharedPreferences.getter(key, defaultValue) ?: defaultValue)
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value.value
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ class Preference<T>(
|
|||
|
||||
value.value = newValue
|
||||
|
||||
sharedPreferences!!.edit { setter(key, newValue) }
|
||||
sharedPreferences.edit { setter(key, newValue) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@
|
|||
|
||||
package com.aliucord.manager.preferences
|
||||
|
||||
import android.os.Environment
|
||||
import com.aliucord.manager.utils.aliucordDir
|
||||
|
||||
var themePref = intPreference("theme")
|
||||
val replaceBg = boolPreference("replace_bg", true)
|
||||
val devModePreference = boolPreference("dev_mode", false)
|
||||
val debuggablePreference = boolPreference("debuggable")
|
||||
val useDexFromStoragePreference = boolPreference("use_dex_from_storage")
|
||||
val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Injector.dex")
|
||||
|
||||
object Preferences {
|
||||
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")
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Juby210
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import com.aliucord.manager.R
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
sealed class Screen(val route: String, @StringRes val displayName: Int) {
|
||||
companion object {
|
||||
val screens by lazy { listOf(Home, About, Settings, Commits, Installer, Store) }
|
||||
}
|
||||
|
||||
object Home : Screen("home", R.string.app_name)
|
||||
object About : Screen("about", R.string.about)
|
||||
object Settings : Screen("settings", R.string.settings)
|
||||
object Commits : Screen("commits", R.string.commits)
|
||||
object Installer : Screen("installer", R.string.installer)
|
||||
object Store : Screen("store", R.string.store)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.aliucord.manager.ui.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
|
|
@ -18,34 +18,43 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.Screen
|
||||
import com.aliucord.manager.ui.screens.destinations.*
|
||||
import com.aliucord.manager.ui.screens.navDestination
|
||||
|
||||
@get:StringRes
|
||||
private val Destination.title
|
||||
get() = when (this) {
|
||||
HomeScreenDestination -> R.string.home
|
||||
InstallerScreenDestination -> R.string.installer
|
||||
CommitsScreenDestination -> R.string.commits
|
||||
StoreScreenDestination -> R.string.store
|
||||
SettingsScreenDestination -> R.string.settings
|
||||
AboutScreenDestination -> R.string.about
|
||||
}
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun AppBar(navController: NavController) {
|
||||
val route = navController.currentBackStackEntryAsState().value?.destination?.route ?: Screen.Home.route
|
||||
val destination = navController.currentBackStackEntryAsState().value?.navDestination ?: HomeScreenDestination
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
MediumTopAppBar(
|
||||
navigationIcon = {
|
||||
if (route != Screen.Home.route) {
|
||||
if (destination != HomeScreenDestination) {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(Icons.Default.ArrowBack, stringResource(R.string.back), tint = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(Screen.screens.first { screen ->
|
||||
screen.route == route
|
||||
}.displayName))
|
||||
Text(stringResource(destination.title))
|
||||
},
|
||||
actions = {
|
||||
val localUriHandler = LocalUriHandler.current
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
navController.navigate(Screen.Store.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
navController.navigate(StoreScreenDestination.route) {
|
||||
popUpTo(HomeScreenDestination.route) { saveState = true }
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
|
@ -89,21 +98,17 @@ fun AppBar(navController: NavController) {
|
|||
DropdownMenuItem(
|
||||
onClick = {
|
||||
isMenuExpanded = false
|
||||
navController.navigate(Screen.About.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
}
|
||||
navController.navigate(AboutScreenDestination.route)
|
||||
},
|
||||
text = { Text(stringResource(Screen.About.displayName)) }
|
||||
text = { Text(stringResource(AboutScreenDestination.title)) }
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
isMenuExpanded = false
|
||||
navController.navigate(Screen.Settings.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
}
|
||||
navController.navigate(SettingsScreenDestination.route)
|
||||
},
|
||||
text = { Text(stringResource(Screen.Settings.displayName)) }
|
||||
text = { Text(stringResource(SettingsScreenDestination.title)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.aliucord.manager.ui.components
|
||||
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.material.ListItem as M2ListItem
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
secondaryText: @Composable (() -> Unit)? = null,
|
||||
singleLineSecondaryText: Boolean = true,
|
||||
overlineText: @Composable (() -> Unit)? = null,
|
||||
trailing: @Composable (() -> Unit)? = null,
|
||||
text: @Composable () -> Unit
|
||||
) = M2ListItem(
|
||||
modifier = modifier,
|
||||
icon = icon,
|
||||
overlineText = if (overlineText != null) {{
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
LocalTextStyle provides MaterialTheme.typography.labelMedium
|
||||
) {
|
||||
overlineText()
|
||||
}
|
||||
}} else null,
|
||||
text = {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
|
||||
LocalTextStyle provides MaterialTheme.typography.bodyLarge
|
||||
) {
|
||||
text()
|
||||
}
|
||||
},
|
||||
secondaryText = if (secondaryText != null) {
|
||||
{
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
LocalTextStyle provides MaterialTheme.typography.bodyMedium
|
||||
) {
|
||||
secondaryText()
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
trailing = trailing,
|
||||
singleLineSecondaryText = singleLineSecondaryText,
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.aliucord.manager.ui.components
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.aliucord.manager.ui.screens.NavGraphs
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionRequired
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.rememberNavHostEngine
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalPermissionsApi::class,
|
||||
ExperimentalAnimationApi::class
|
||||
)
|
||||
@Composable
|
||||
fun ManagerScaffold() {
|
||||
val navController = rememberNavController()
|
||||
|
||||
Scaffold(
|
||||
topBar = { AppBar(navController) }
|
||||
) { paddingValues ->
|
||||
val storagePermissionState = rememberPermissionState(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
|
||||
PermissionRequired(
|
||||
permissionState = storagePermissionState,
|
||||
permissionNotAvailableContent = { GrantPermission(storagePermissionState) },
|
||||
permissionNotGrantedContent = { GrantPermission(storagePermissionState) }
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = 12.dp, vertical = 14.dp),
|
||||
navGraph = NavGraphs.root,
|
||||
navController = navController,
|
||||
engine = rememberNavHostEngine()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,9 @@ 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.models.Plugin
|
||||
import com.aliucord.manager.ui.components.plugins.Changelog
|
||||
import com.aliucord.manager.ui.components.plugins.PluginCard
|
||||
import com.aliucord.manager.utils.Plugin
|
||||
|
||||
@Composable
|
||||
fun PluginsList() = Column(
|
||||
|
|
@ -46,7 +46,7 @@ fun PluginsList() = Column(
|
|||
trailingIcon = { Icon(Icons.Default.Search, stringResource(R.string.search_plugins)) },
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions { focusManager.clearFocus() },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
textColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
|
|
@ -65,8 +65,6 @@ fun PluginsList() = Column(
|
|||
|| authors.any { a -> a.name.contains(search, true) }
|
||||
}
|
||||
}) { plugin ->
|
||||
// TODO - make this get pluginEnabled from Aliucord preferences somehow
|
||||
var isChecked by remember { mutableStateOf(true) }
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
var showChangelogDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,24 +6,26 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.aliucord.manager.R
|
||||
|
||||
@Composable
|
||||
fun ContributorEntry(name: String) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clickable { uriHandler.openUri("https://github.com/$name") },
|
||||
painter = rememberImagePainter(
|
||||
data = "https://github.com/$name.png",
|
||||
builder = {
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
),
|
||||
contentDescription = "Contributor",
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clickable { uriHandler.openUri("https://github.com/$name") }
|
||||
contentDescription = stringResource(R.string.contributor)
|
||||
)
|
||||
}
|
||||
|
|
@ -15,15 +15,14 @@ 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.models.GithubUser
|
||||
import com.aliucord.manager.models.github.GithubUser
|
||||
import com.aliucord.manager.utils.Github
|
||||
import com.aliucord.manager.utils.httpClient
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ExperimentalFoundationApi
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ContributorsCard() = ElevatedCard(
|
||||
modifier = Modifier.wrapContentHeight().fillMaxWidth()
|
||||
|
|
@ -35,7 +34,7 @@ fun ContributorsCard() = ElevatedCard(
|
|||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.contributors),
|
||||
text = stringResource(R.string.contributors),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ fun TeamCard() = ElevatedCard(
|
|||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.team),
|
||||
text = stringResource(R.string.team),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Column {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
UserEntry("Juby210", "Fox")
|
||||
UserEntry("Vendicated", "the ven")
|
||||
UserEntry("mwittrien", "your memory gon")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.aliucord.manager.ui.components.about
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -12,9 +15,7 @@ import coil.compose.rememberImagePainter
|
|||
import coil.transform.CircleCropTransformation
|
||||
|
||||
@Composable
|
||||
fun UserEntry(name: String, roles: String) = Column(
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
fun UserEntry(name: String, roles: String) = Column {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
|
|||
|
|
@ -1,72 +1,94 @@
|
|||
package com.aliucord.manager.ui.components.installer
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.Screen
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
enum class DownloadMethod { DOWNLOAD, SELECT }
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun InstallerDialog(
|
||||
visible: MutableState<Boolean>,
|
||||
navController: NavController
|
||||
) = AlertDialog(
|
||||
title = {
|
||||
Row (
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(painterResource(R.drawable.ic_discord), "Discord")
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text("Select Installation Method")
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Column(
|
||||
modifier = Modifier.wrapContentWidth().padding(32.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
val modifier = Modifier.fillMaxWidth()
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (DownloadMethod) -> Unit
|
||||
) {
|
||||
var selectedMethod by remember { mutableStateOf(DownloadMethod.DOWNLOAD) }
|
||||
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_download_24dp),
|
||||
contentDescription = stringResource(R.string.download_method)
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
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)
|
||||
).forEach { (method, name) ->
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.clickable {
|
||||
selectedMethod = method
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
RadioButton(
|
||||
selected = selectedMethod == method,
|
||||
onClick = { selectedMethod = method }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
navController.currentBackStackEntry?.arguments
|
||||
navController.navigate(Screen.Installer.route)
|
||||
onConfirm(selectedMethod)
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Text("Download")
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
navController.navigate(Screen.Installer.route)
|
||||
}
|
||||
onClick = onDismissRequest,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Text("From installed app")
|
||||
Text(stringResource(R.string.dismiss))
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
navController.navigate(Screen.Installer.route)
|
||||
}
|
||||
) {
|
||||
Text("From storage")
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = { visible.value = false }
|
||||
)
|
||||
},
|
||||
onDismissRequest = onDismissRequest
|
||||
)
|
||||
}
|
||||
|
|
@ -19,20 +19,19 @@ import androidx.compose.ui.unit.dp
|
|||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.utils.Plugin
|
||||
import java.util.regex.Pattern
|
||||
import com.aliucord.manager.models.Plugin
|
||||
|
||||
private val hyperLinkPattern = Pattern.compile("\\[(.+?)]\\((.+?\\))")
|
||||
private val hyperLinkPattern = Regex("\\[(.+?)]\\((.+?\\))")
|
||||
|
||||
@Suppress("RegExpRedundantEscape") // It is very much not redundant and causes a crash lol
|
||||
private val headerStylePattern = Pattern.compile("\\{(improved|added|fixed)( marginTop)?\\}")
|
||||
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
|
||||
|
||||
with(hyperLinkPattern.matcher(content)) {
|
||||
with(hyperLinkPattern.toPattern().matcher(content)) {
|
||||
while (find()) {
|
||||
val start = start()
|
||||
val end = end()
|
||||
|
|
@ -82,7 +81,7 @@ fun Changelog(
|
|||
painter = rememberImagePainter(mediaUrl) {
|
||||
transformations(RoundedCornersTransformation(14.dp.value))
|
||||
},
|
||||
contentDescription = "Changelog Media"
|
||||
contentDescription = stringResource(R.string.changelog_media)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.models.Plugin
|
||||
import com.aliucord.manager.ui.components.Switch
|
||||
import com.aliucord.manager.utils.Plugin
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -27,8 +27,7 @@ fun PluginCard(
|
|||
onShowChangelog: () -> Unit
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.aliucord.manager.preferences.Preference
|
||||
import com.aliucord.manager.ui.components.ListItem
|
||||
import com.aliucord.manager.ui.components.Switch
|
||||
|
||||
@Composable
|
||||
fun ListItem(
|
||||
trailing: @Composable () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
Text("Title", style = MaterialTheme.typography.labelMedium)
|
||||
Text("Subtext", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
trailing()
|
||||
fun PreferenceItem(
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
title: @Composable () -> Unit,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
preference: Preference<Boolean>
|
||||
) = ListItem(
|
||||
icon = icon,
|
||||
modifier = Modifier.clickable {
|
||||
preference.set(!preference.get())
|
||||
},
|
||||
text = title,
|
||||
secondaryText = description,
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = preference.get(),
|
||||
onCheckedChange = preference::set
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.aliucord.manager.preferences.Preference
|
||||
import com.aliucord.manager.ui.components.Switch
|
||||
|
||||
@Composable
|
||||
fun SettingSwitch(
|
||||
modifier: Modifier = Modifier,
|
||||
preference: Preference<Boolean>
|
||||
) = Switch(
|
||||
modifier = modifier,
|
||||
checked = preference.get(),
|
||||
onCheckedChange = preference::set
|
||||
)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ThemeDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (Theme) -> Unit
|
||||
) {
|
||||
var selectedTheme by remember { mutableStateOf(Theme.from(Prefs.theme.get())) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,28 +1,30 @@
|
|||
package com.aliucord.manager.ui.components.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.preferences.themePref
|
||||
import com.aliucord.manager.preferences.Prefs
|
||||
import com.aliucord.manager.ui.components.ListItem
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ThemeSetting(openDialog: MutableState<Boolean>) {
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.theme)) },
|
||||
secondaryText = { Text(stringResource(R.string.theme_setting_description))},
|
||||
trailing = {
|
||||
FilledTonalButton(
|
||||
onClick = { openDialog.value = true }
|
||||
) {
|
||||
Text(Theme.from(themePref.get()).displayName)
|
||||
}
|
||||
fun ThemeSetting(
|
||||
onClick: () -> Unit,
|
||||
) = ListItem(
|
||||
modifier = Modifier.clickable { onClick() },
|
||||
text = { Text(stringResource(R.string.theme)) },
|
||||
secondaryText = { Text(stringResource(R.string.theme_setting_description)) },
|
||||
trailing = {
|
||||
FilledTonalButton(
|
||||
onClick = onClick
|
||||
) {
|
||||
Text(Theme.from(Prefs.theme.get()).displayName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -14,8 +13,9 @@ import androidx.compose.ui.unit.dp
|
|||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.about.ContributorsCard
|
||||
import com.aliucord.manager.ui.components.about.TeamCard
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@Destination
|
||||
@Composable
|
||||
fun AboutScreen() {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
|
|
|||
|
|
@ -1,97 +1,98 @@
|
|||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.Keep
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.models.Commit
|
||||
import androidx.paging.*
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.models.github.Commit
|
||||
import com.aliucord.manager.ui.components.ListItem
|
||||
import com.aliucord.manager.utils.Github
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Keep
|
||||
private data class CommitData(
|
||||
val commit: Commit,
|
||||
val buildSha: String?
|
||||
)
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@ExperimentalMaterialApi
|
||||
@Destination
|
||||
@Composable
|
||||
fun CommitsScreen() {
|
||||
val commits = remember { mutableStateListOf<CommitData>() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (commits.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(12.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = { CircularProgressIndicator() }
|
||||
val pager = remember {
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 30,
|
||||
enablePlaceholders = true,
|
||||
maxSize = 200
|
||||
)
|
||||
) {
|
||||
object : PagingSource<Int, Commit>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Commit> {
|
||||
val pageNumber = params.key ?: 0
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val buildCommits = Github.getCommits(
|
||||
"sha" to "builds",
|
||||
"path" to "Aliucord.dex",
|
||||
"per_page" to "50"
|
||||
)
|
||||
val commits2 = Github.getCommits("per_page" to "50")
|
||||
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
|
||||
|
||||
commits.addAll(commits2.map { c ->
|
||||
CommitData(
|
||||
commit = c,
|
||||
buildSha = buildCommits.find { bc -> bc.commit.message.substring(6) == c.sha }?.sha
|
||||
)
|
||||
})
|
||||
} catch (e: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to get commits", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
items(commits.toList()) { data ->
|
||||
val commit = data.commit
|
||||
val localUriHandler = LocalUriHandler.current
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
localUriHandler.openUri(commit.htmlUrl)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
commit.sha.substring(0, 7),
|
||||
color = if (data.buildSha != null) MaterialTheme.colorScheme.primary else Color.Unspecified
|
||||
)
|
||||
},
|
||||
secondaryText = { Text("${commit.commit.message.split("\n").first()} - ${commit.author.name}") },
|
||||
return LoadResult.Page(
|
||||
data = response,
|
||||
prevKey = prevKey,
|
||||
nextKey = nextKey
|
||||
)
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, Commit>) = state.anchorPosition?.let {
|
||||
state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
|
||||
|
||||
LazyColumn(
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
items(lazyPagingItems) { commitData ->
|
||||
val localUriHandler = LocalUriHandler.current
|
||||
|
||||
if (commitData == null) return@items
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
localUriHandler.openUri(commitData.htmlUrl)
|
||||
},
|
||||
overlineText = { Text(commitData.sha.substring(0, 7)) },
|
||||
text = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author.name}")}
|
||||
)
|
||||
}
|
||||
|
||||
if (lazyPagingItems.loadState.append == LoadState.Loading) item {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,8 @@ package com.aliucord.manager.ui.screens
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
|
|
@ -24,26 +25,28 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.core.net.toFile
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.models.Version
|
||||
import com.aliucord.manager.preferences.devModePreference
|
||||
import com.aliucord.manager.ui.Screen
|
||||
import com.aliucord.manager.models.github.Version
|
||||
import com.aliucord.manager.preferences.Prefs
|
||||
import com.aliucord.manager.ui.components.PluginsList
|
||||
import com.aliucord.manager.ui.components.installer.DownloadMethod
|
||||
import com.aliucord.manager.ui.components.installer.InstallerDialog
|
||||
import com.aliucord.manager.ui.screens.destinations.CommitsScreenDestination
|
||||
import com.aliucord.manager.ui.screens.destinations.InstallerScreenDestination
|
||||
import com.aliucord.manager.utils.Github
|
||||
import com.aliucord.manager.utils.gson
|
||||
import com.aliucord.manager.utils.httpClient
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ExperimentalPermissionsApi
|
||||
@ExperimentalFoundationApi
|
||||
@Destination(start = true)
|
||||
@Composable
|
||||
fun HomeScreen(navController: NavController) {
|
||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val packageManager = LocalContext.current.packageManager
|
||||
var supportedVersion by remember { mutableStateOf<String?>(null) }
|
||||
val installedVersion = remember {
|
||||
|
|
@ -54,9 +57,7 @@ fun HomeScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
|
||||
val showOptionsDialog = remember { mutableStateOf(false) }
|
||||
|
||||
if (supportedVersion == null) LaunchedEffect(Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
val version = gson.fromJson(httpClient.get<String>(Github.dataUrl), Version::class.java)
|
||||
|
||||
|
|
@ -69,7 +70,26 @@ fun HomeScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
|
||||
if (showOptionsDialog.value) InstallerDialog(showOptionsDialog, navController)
|
||||
var showOptionsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showOptionsDialog) {
|
||||
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
|
||||
navigator.navigate(InstallerScreenDestination(uri.toFile()))
|
||||
}
|
||||
|
||||
InstallerDialog(
|
||||
onDismissRequest = { showOptionsDialog = false },
|
||||
onConfirm = { method ->
|
||||
if (method == DownloadMethod.DOWNLOAD) {
|
||||
navigator.navigate(InstallerScreenDestination())
|
||||
} else {
|
||||
filePicker.launch(arrayOf("application/octet-stream"))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
|
|
@ -78,7 +98,9 @@ fun HomeScreen(navController: NavController) {
|
|||
modifier = Modifier.wrapContentHeight()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Column {
|
||||
|
|
@ -113,81 +135,90 @@ fun HomeScreen(navController: NavController) {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
if (devModePreference.get()) {
|
||||
showOptionsDialog.value = true
|
||||
} else {
|
||||
navController.navigate(Screen.Installer.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f, true),
|
||||
onClick = {
|
||||
if (Prefs.devMode.get()) {
|
||||
showOptionsDialog = true
|
||||
} else {
|
||||
navigator.navigate(InstallerScreenDestination())
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(drawable),
|
||||
contentDescription = stringResource(description),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
painter = painterResource(drawable),
|
||||
contentDescription = stringResource(description),
|
||||
tint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
|
||||
Text(stringResource(description))
|
||||
Text(stringResource(description))
|
||||
}
|
||||
|
||||
if (installedVersion == "-") Button(
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
onClick = { navigator.navigate(CommitsScreenDestination) },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
painter = painterResource(R.drawable.ic_update_24dp),
|
||||
contentDescription = "Commits",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints {
|
||||
if (installedVersion != "-") {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
if (installedVersion != "-") {
|
||||
val context = LocalContext.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
|
||||
onClick = {
|
||||
val packageURI = Uri.parse("package:com.aliucord")
|
||||
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI)
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
|
||||
onClick = {
|
||||
val packageURI = Uri.parse("package:com.aliucord")
|
||||
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI)
|
||||
|
||||
context.startActivity(uninstallIntent)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Uninstall",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
packageManager.getLaunchIntentForPackage("com.aliucord")?.let {
|
||||
context.startActivity(it)
|
||||
} ?: Toast.makeText(context, "Failed to launch Aliucord", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
painter = painterResource(R.drawable.ic_launch_24dp),
|
||||
contentDescription = "Launch",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
context.startActivity(uninstallIntent)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Uninstall",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
navController.navigate(Screen.Commits.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
}
|
||||
packageManager.getLaunchIntentForPackage("com.aliucord")?.let {
|
||||
context.startActivity(it)
|
||||
} ?: Toast.makeText(context, "Failed to launch Aliucord", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
painter = painterResource(R.drawable.ic_launch_24dp),
|
||||
contentDescription = "Launch",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = { navigator.navigate(CommitsScreenDestination) },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
|
|
@ -195,10 +226,6 @@ fun HomeScreen(navController: NavController) {
|
|||
contentDescription = "Commits",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
|
||||
if (this@BoxWithConstraints.minWidth > 20.dp) {
|
||||
Text("Commits", color = MaterialTheme.colorScheme.onSecondaryContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +233,11 @@ fun HomeScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
|
||||
ElevatedCard(Modifier.fillMaxWidth().weight(1f)) {
|
||||
ElevatedCard(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,31 @@
|
|||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.navigation.NavController
|
||||
import com.aliucord.libzip.Zip
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.Signer
|
||||
import com.aliucord.manager.preferences.replaceBg
|
||||
import com.aliucord.manager.ui.Screen
|
||||
import com.aliucord.manager.utils.DownloadUtils
|
||||
import com.aliucord.manager.utils.Github
|
||||
import com.aliucord.manager.preferences.Prefs
|
||||
import com.aliucord.manager.ui.screens.destinations.HomeScreenDestination
|
||||
import com.aliucord.manager.utils.*
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun InstallerScreen(navController: NavController) {
|
||||
fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) {
|
||||
var log by remember { mutableStateOf("") }
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
@ -35,31 +33,36 @@ fun InstallerScreen(navController: NavController) {
|
|||
launch(Dispatchers.IO) {
|
||||
val supportedVersion = Github.version.versionCode
|
||||
|
||||
log += "Checking for cached APK...\n"
|
||||
val discordApk = apk ?: run {
|
||||
log += "Checking for cached APK...\n"
|
||||
|
||||
val discordApk = File(context.externalCacheDir, "discord-${supportedVersion}.apk").also { file ->
|
||||
if (file.exists()) return@also
|
||||
File(context.externalCacheDir, "discord-${supportedVersion}.apk").also { file ->
|
||||
val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0)
|
||||
|
||||
val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0)
|
||||
if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also
|
||||
|
||||
if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also
|
||||
log += "Downloading Discord APK...\n"
|
||||
|
||||
log += "Downloading APK...\n"
|
||||
DownloadUtils.downloadDiscordApk(context, supportedVersion)
|
||||
|
||||
DownloadUtils.downloadDiscordApk(context, supportedVersion)
|
||||
|
||||
log += "Finished downloading APK\n"
|
||||
log += "Done\n"
|
||||
}
|
||||
}
|
||||
|
||||
val manifest = File(context.externalCacheDir, "AndroidManifest.xml").also { file ->
|
||||
if (file.exists()) return@also
|
||||
val useDebuggableManifest = Prefs.debuggable.get()
|
||||
val manifest =
|
||||
File(
|
||||
context.externalCacheDir,
|
||||
if (useDebuggableManifest) "AndroidManifest-debuggable.xml" else "AndroidManifest.xml"
|
||||
).also { file ->
|
||||
if (file.exists()) return@also
|
||||
|
||||
log += "Downloading patched AndroidManifest.xml...\n"
|
||||
log += "Downloading patched manifest...\n"
|
||||
|
||||
DownloadUtils.downloadManifest(context)
|
||||
DownloadUtils.downloadManifest(context, useDebuggableManifest)
|
||||
|
||||
log += "Finished downloading manifest\n"
|
||||
}
|
||||
log += "Done\n"
|
||||
}
|
||||
|
||||
val injector = File(context.externalCacheDir, "Injector.dex").also { file ->
|
||||
if (file.exists()) return@also
|
||||
|
|
@ -68,13 +71,10 @@ fun InstallerScreen(navController: NavController) {
|
|||
|
||||
DownloadUtils.downloadInjector(context)
|
||||
|
||||
log += "Finished downloading injector\n"
|
||||
log += "Done\n"
|
||||
}
|
||||
|
||||
log += "Copying original APK ()\n"
|
||||
|
||||
|
||||
val outputDir = File(Environment.getExternalStorageDirectory(), "Aliucord").also {
|
||||
val outputDir = aliucordDir.also {
|
||||
if (!it.exists() && !it.mkdirs()) throw FileNotFoundException()
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ fun InstallerScreen(navController: NavController) {
|
|||
close()
|
||||
}
|
||||
|
||||
if (replaceBg.get()) {
|
||||
if (Prefs.replaceBg.get()) {
|
||||
log += "Replacing icon background\n"
|
||||
|
||||
val icon1Bytes = assetManager.open("icon1.png").readBytes()
|
||||
|
|
@ -158,7 +158,8 @@ fun InstallerScreen(navController: NavController) {
|
|||
// androguard arsc resources.arsc --id 0x7f0f0000 (icon1)
|
||||
// androguard arsc resources.arsc --id 0x7f0f0002 and androguard arsc resources.arsc --id 0x7f0f0006 (icon2)
|
||||
val icon1Entries = listOf("MbV.png", "kbF.png", "_eu.png", "EtS.png")
|
||||
val icon2Entries = listOf("_h_.png", "9MB.png", "Dy7.png", "kC0.png", "oEH.png", "RG0.png", "ud_.png", "W_3.png")
|
||||
val icon2Entries =
|
||||
listOf("_h_.png", "9MB.png", "Dy7.png", "kC0.png", "oEH.png", "RG0.png", "ud_.png", "W_3.png")
|
||||
|
||||
with(Zip(outputApk.absolutePath, 0, 'a')) {
|
||||
icon1Entries.forEach { entry -> deleteEntry("res/$entry") }
|
||||
|
|
@ -171,9 +172,8 @@ fun InstallerScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
|
||||
log += "Signing APK\n"
|
||||
log += "Signing APK...\n"
|
||||
|
||||
Signer.newKeystore(File(context.filesDir, "ks.keystore"))
|
||||
Signer.signApk(outputApk)
|
||||
|
||||
log += "Signed APK\n"
|
||||
|
|
@ -191,36 +191,23 @@ fun InstallerScreen(navController: NavController) {
|
|||
context.startActivity(intent)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
navController.navigate(Screen.Home.route)
|
||||
navigator.navigate(HomeScreenDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(log)
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp),
|
||||
progress = 0.5f
|
||||
)
|
||||
// LinearProgressIndicator(
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .height(8.dp),
|
||||
// progress = 0.5f
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
private fun Zip.writeEntry(entryName: String, stream: InputStream) {
|
||||
openEntry(entryName)
|
||||
stream.readBytes().let { bytes -> writeEntry(bytes, bytes.size.toLong()) }
|
||||
closeEntry()
|
||||
}
|
||||
|
||||
private fun Zip.writeEntry(entryName: String, bytes: ByteArray) {
|
||||
openEntry(entryName)
|
||||
writeEntry(bytes, bytes.size.toLong())
|
||||
closeEntry()
|
||||
}
|
||||
|
|
@ -5,155 +5,108 @@
|
|||
|
||||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toFile
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.preferences.*
|
||||
import com.aliucord.manager.ui.components.Switch
|
||||
import com.aliucord.manager.ui.components.settings.SettingSwitch
|
||||
import com.aliucord.manager.preferences.Prefs
|
||||
import com.aliucord.manager.ui.components.ListItem
|
||||
import com.aliucord.manager.ui.components.settings.PreferenceItem
|
||||
import com.aliucord.manager.ui.components.settings.ThemeDialog
|
||||
import com.aliucord.manager.ui.components.settings.ThemeSetting
|
||||
import com.aliucord.manager.ui.theme.Theme
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ExperimentalMaterialApi
|
||||
@OptIn(ExperimentalMaterial3Api::class, androidx.compose.material.ExperimentalMaterialApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SettingsScreen() = Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
val openDialog = remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
var showThemeDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (openDialog.value) {
|
||||
var selected by remember { mutableStateOf(Theme.from(themePref.get())) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { openDialog.value = false },
|
||||
title = { Text("Theme") },
|
||||
text = {
|
||||
Column {
|
||||
Theme.values().forEach { theme ->
|
||||
Row(
|
||||
modifier = Modifier.clickable {
|
||||
selected = theme
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
theme.displayName,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
RadioButton(
|
||||
selected = theme == selected,
|
||||
onClick = { selected = theme }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
openDialog.value = false
|
||||
themePref.set(selected.ordinal)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Confirm"
|
||||
)
|
||||
Spacer(Modifier.padding(4.dp))
|
||||
Text("Apply")
|
||||
}
|
||||
}
|
||||
if (showThemeDialog) {
|
||||
ThemeDialog(
|
||||
onDismissRequest = { showThemeDialog = false },
|
||||
onConfirm = { Prefs.theme.set(it.ordinal) }
|
||||
)
|
||||
}
|
||||
|
||||
ThemeSetting(openDialog)
|
||||
ThemeSetting(onClick = { showThemeDialog = true })
|
||||
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.use_black)) },
|
||||
secondaryText = { Text(stringResource(R.string.use_black_description)) },
|
||||
trailing = { SettingSwitch(preference = Preferences.useBlack) }
|
||||
PreferenceItem(
|
||||
title = { Text(stringResource(R.string.use_black)) },
|
||||
description = { Text(stringResource(R.string.use_black_description)) },
|
||||
preference = Prefs.useBlack
|
||||
)
|
||||
|
||||
Divider()
|
||||
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.replace_bg)) },
|
||||
trailing = { SettingSwitch(preference = replaceBg) }
|
||||
PreferenceItem(
|
||||
title = { Text(stringResource(R.string.replace_bg)) },
|
||||
preference = Prefs.replaceBg
|
||||
)
|
||||
|
||||
val devModeOn = devModePreference.get()
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.developer_mode)) },
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = devModeOn,
|
||||
onCheckedChange = devModePreference::set
|
||||
)
|
||||
}
|
||||
val devModeOn = Prefs.devMode.get()
|
||||
|
||||
PreferenceItem(
|
||||
title = { Text(stringResource(R.string.developer_mode)) },
|
||||
preference = Prefs.devMode
|
||||
)
|
||||
|
||||
if (devModeOn) {
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.debuggable)) },
|
||||
trailing = { SettingSwitch(preference = debuggablePreference) }
|
||||
PreferenceItem(
|
||||
title = { Text(stringResource(R.string.debuggable)) },
|
||||
description = { Text(stringResource(R.string.debuggable_description)) },
|
||||
preference = Prefs.debuggable
|
||||
)
|
||||
|
||||
val udfs = useDexFromStoragePreference
|
||||
val udfs = Prefs.useDexFromStorage
|
||||
|
||||
ListItem(
|
||||
text = { Text(stringResource(R.string.use_dex_from_storage)) },
|
||||
trailing = { SettingSwitch(preference = udfs) }
|
||||
PreferenceItem(
|
||||
title = { Text(stringResource(R.string.use_dex_from_storage)) },
|
||||
preference = Prefs.useDexFromStorage
|
||||
)
|
||||
|
||||
val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri != null) Prefs.dexLocation.set(uri.path!!)
|
||||
}
|
||||
|
||||
val color = if (udfs.get()) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground.copy(ContentAlpha.disabled)
|
||||
}
|
||||
|
||||
val test = remember { mutableStateOf("") }
|
||||
val result = remember { mutableStateOf<Uri?>(null) }
|
||||
val context = LocalContext.current
|
||||
val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
|
||||
test.value = uri.toFile().absolutePath
|
||||
}
|
||||
|
||||
ListItem(
|
||||
modifier = if (udfs.get()) Modifier.clickable {
|
||||
picker.launch(arrayOf("application/octet-stream"))
|
||||
} else Modifier,
|
||||
text = { Text(stringResource(R.string.dex_location), color = color) },
|
||||
secondaryText = { Text(dexLocationPreference.get(), color = color) }
|
||||
secondaryText = { Text(Prefs.dexLocation.get(), color = color) }
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
onClick = { /*TODO*/ }
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
onClick = {
|
||||
context.cacheDir.deleteRecursively()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.utils.httpClient
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -33,6 +34,7 @@ private data class Tree(
|
|||
)
|
||||
}
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun StoreScreen() {
|
||||
val plugins = remember { mutableStateListOf<Tree.File>() }
|
||||
|
|
|
|||
|
|
@ -11,16 +11,24 @@ import kotlin.coroutines.resume
|
|||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object DownloadUtils {
|
||||
private const val org = "Aliucord"
|
||||
private const val repo = "Aliucord"
|
||||
|
||||
private const val backendHost = "https://aliucord.com/"
|
||||
private const val contentUrl = "https://raw.githubusercontent.com/${org}/${repo}"
|
||||
|
||||
suspend fun downloadDiscordApk(ctx: Context, version: String) =
|
||||
download(ctx, "$backendHost/download/discord?v=$version", "discord-$version.apk")
|
||||
|
||||
suspend fun downloadManifest(ctx: Context) =
|
||||
download(ctx, Github.getDownloadUrl("builds", "AndroidManifest.xml"), "AndroidManifest.xml")
|
||||
suspend fun downloadManifest(ctx: Context, useDebuggableManifest: Boolean) {
|
||||
if (useDebuggableManifest)
|
||||
download(ctx, "$contentUrl/main/.assets/AndroidManifest-debuggable.xml", "AndroidManifest-debuggable.xml")
|
||||
else {
|
||||
download(ctx, "$contentUrl/main/.assets/AndroidManifest.xml", "AndroidManifest.xml")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadInjector(ctx: Context) =
|
||||
download(ctx, Github.getDownloadUrl("builds", "Injector.dex"), "Injector.dex")
|
||||
suspend fun downloadInjector(ctx: Context) = download(ctx, "$contentUrl/builds/Injector.dex", "Injector.dex")
|
||||
|
||||
private suspend fun download(ctx: Context, url: String, fileName: String): File {
|
||||
val downloadManager = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
|
@ -42,7 +50,6 @@ object DownloadUtils {
|
|||
ctx.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||
|
||||
receiver.downloadId = DownloadManager.Request(url.toUri()).run {
|
||||
// Users could cancel downloads, guhhhh
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
||||
setDestinationUri(File(ctx.externalCacheDir, fileName).toUri())
|
||||
downloadManager.enqueue(this)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package com.aliucord.manager.utils
|
||||
|
||||
import com.aliucord.manager.models.Commit
|
||||
import com.aliucord.manager.models.Version
|
||||
import com.aliucord.manager.models.github.Commit
|
||||
import com.aliucord.manager.models.github.Version
|
||||
import io.ktor.client.request.*
|
||||
import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
|
|
@ -28,14 +28,14 @@ object Github {
|
|||
gson.fromJson(InputStreamReader(URL(dataUrl).openStream()), Version::class.java)
|
||||
}
|
||||
|
||||
private val commitsCache = HashMap<String, Array<Commit>>()
|
||||
private val commitsCache = HashMap<String, List<Commit>>()
|
||||
|
||||
suspend fun getCommits(vararg params: Pair<String, String>): Array<Commit> {
|
||||
suspend fun getCommits(vararg params: Pair<String, String>): List<Commit> {
|
||||
val query = params.joinToString("&", "?") { it.first + "=" + it.second }
|
||||
|
||||
if (commitsCache.containsKey(query)) return commitsCache[query] ?: emptyArray()
|
||||
if (commitsCache.containsKey(query)) return commitsCache[query] ?: emptyList()
|
||||
|
||||
val res = httpClient.get<Array<Commit>>(commitsUrl) {
|
||||
val res = httpClient.get<List<Commit>>(commitsUrl) {
|
||||
params.forEach { (key, value) -> parameter(key, value) }
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +43,4 @@ object Github {
|
|||
|
||||
return res
|
||||
}
|
||||
|
||||
fun getDownloadUrl(ref: String, file: String) = "https://raw.githubusercontent.com/$org/$repo/$ref/$file"
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) 2021 Juby210 & Vendicated
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
package com.aliucord.manager
|
||||
package com.aliucord.manager.utils
|
||||
|
||||
import com.aliucord.libzip.Zip
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
|
|
@ -28,10 +28,12 @@ import java.util.jar.Attributes
|
|||
import java.util.jar.JarFile
|
||||
import java.util.jar.Manifest
|
||||
|
||||
private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey)
|
||||
|
||||
object Signer {
|
||||
private val stripPattern = Regex("^META-INF/(.*)[.](MF|SF|RSA|DSA)$")
|
||||
|
||||
fun newKeystore(out: File?) {
|
||||
private fun newKeystore(out: File?) {
|
||||
val password = "password".toCharArray()
|
||||
val key = createKey()
|
||||
|
||||
|
|
@ -42,12 +44,12 @@ object Signer {
|
|||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun createKey(): KeySet {
|
||||
val pair = KeyPairGenerator.getInstance("RSA").run {
|
||||
initialize(2048)
|
||||
generateKeyPair()
|
||||
}
|
||||
|
||||
var serialNumber: BigInteger
|
||||
|
||||
do serialNumber = SecureRandom().nextInt().toBigInteger()
|
||||
|
|
@ -87,10 +89,12 @@ object Signer {
|
|||
)
|
||||
var zip = Zip(apkFile.absolutePath, 6, 'r')
|
||||
val dig = MessageDigest.getInstance("SHA1")
|
||||
val digests = LinkedHashMap<String, String>()
|
||||
val filesToRemove = ArrayList<String>()
|
||||
val digests = linkedMapOf<String, String>()
|
||||
val filesToRemove = arrayListOf<String>()
|
||||
|
||||
repeat(zip.totalEntries) { i ->
|
||||
zip.openEntryByIndex(i)
|
||||
|
||||
val name = zip.entryName
|
||||
|
||||
if (stripPattern.matches(name))
|
||||
|
|
@ -111,7 +115,7 @@ object Signer {
|
|||
attrs[Attributes.Name("Created-By")] = "Aliucord Installer"
|
||||
val digestAttr = Attributes.Name("SHA1-Digest")
|
||||
|
||||
for ((name, value) in digests) {
|
||||
digests.forEach { (name, value) ->
|
||||
val attributes = Attributes()
|
||||
|
||||
attributes[digestAttr] = value
|
||||
|
|
@ -126,11 +130,13 @@ object Signer {
|
|||
zip.writeEntry(baos.toByteArray(), baos.size().toLong())
|
||||
zip.closeEntry()
|
||||
}
|
||||
|
||||
val manifestHash = getManifestHash(manifest, dig)
|
||||
val tmpManifest = Manifest().apply {
|
||||
mainAttributes.putAll(attrs)
|
||||
}
|
||||
val manifestMainHash = getManifestHash(tmpManifest, dig)
|
||||
|
||||
manifest = Manifest()
|
||||
attrs = manifest.mainAttributes
|
||||
attrs[Attributes.Name.SIGNATURE_VERSION] = "1.0"
|
||||
|
|
@ -142,7 +148,9 @@ object Signer {
|
|||
attributes[digestAttr] = value
|
||||
manifest.entries[key] = attributes
|
||||
}
|
||||
|
||||
var sigBytes: ByteArray
|
||||
|
||||
ByteArrayOutputStream().use { sigStream ->
|
||||
manifest.write(sigStream)
|
||||
sigBytes = sigStream.toByteArray()
|
||||
|
|
@ -150,6 +158,7 @@ object Signer {
|
|||
zip.writeEntry(sigBytes, sigStream.size().toLong())
|
||||
zip.closeEntry()
|
||||
}
|
||||
|
||||
val signature = signSigFile(keySet, sigBytes)
|
||||
zip.openEntry("META-INF/CERT.RSA")
|
||||
zip.writeEntry(signature, signature.size.toLong())
|
||||
11
app/src/main/kotlin/com/aliucord/manager/utils/ZipUtils.kt
Normal file
11
app/src/main/kotlin/com/aliucord/manager/utils/ZipUtils.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.aliucord.manager.utils
|
||||
|
||||
import com.aliucord.libzip.Zip
|
||||
import java.io.InputStream
|
||||
|
||||
fun Zip.writeEntry(entryName: String, stream: InputStream) = writeEntry(entryName, stream.readBytes())
|
||||
fun Zip.writeEntry(entryName: String, bytes: ByteArray) {
|
||||
openEntry(entryName)
|
||||
writeEntry(bytes, bytes.size.toLong())
|
||||
closeEntry()
|
||||
}
|
||||
|
|
@ -1,25 +1,21 @@
|
|||
<resources>
|
||||
<string name="app_name">Aliucord Manager</string>
|
||||
<string name="clear_files_cache">Clear files cache (Injector.dex and patched manifest)</string>
|
||||
<string name="clear_files_cache">Clear files cache</string>
|
||||
<string name="commits">Commits</string>
|
||||
<string name="dex_location">Dex location</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="permission_required">You need to grant storage permission to use this app</string>
|
||||
<string name="permission_grant">Grant permission</string>
|
||||
<string name="replace_bg">Replace icon background with Aliucord\'s</string>
|
||||
<string name="replace_bg">Replace app icon with Aliucord logo</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="show_menu">Show menu</string>
|
||||
<string name="theme">Theme</string>
|
||||
<string name="use_dex_from_storage">Use Aliucord.dex from storage</string>
|
||||
<string name="plugins">Plugins</string>
|
||||
<string name="launch_plugins">Launch Plugins Page</string>
|
||||
<string name="plugin_title">%1$s v%2$s</string>
|
||||
<string name="plugin_authors">By %1$s</string>
|
||||
<string name="uninstall_plugin">Uninstall %1$s</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="view_plugin_changelog">View changelog for %1$s</string>
|
||||
<string name="delete_plugin_body">Are you sure you want to uninstall this plugin? This action cannot be undone.</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="support_server">Support Server</string>
|
||||
<string name="back">Back</string>
|
||||
|
|
@ -27,12 +23,12 @@
|
|||
<string name="install">Install</string>
|
||||
<string name="reinstall">Reinstall</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="developer_mode">Enable Developer Mode</string>
|
||||
<string name="debuggable">Make Aliucord debuggable</string>
|
||||
<string name="developer_mode">Developer Mode</string>
|
||||
<string name="debuggable">Debuggable</string>
|
||||
<string name="debuggable_description">Use debuggable manifest</string>
|
||||
<string name="about">About</string>
|
||||
<string name="team">Team</string>
|
||||
<string name="contributors">Contributors</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="search_plugins">Search Plugins</string>
|
||||
<string name="source_code">Source Code</string>
|
||||
<string name="installer">Installer</string>
|
||||
|
|
@ -41,4 +37,13 @@
|
|||
<string name="theme_setting_description">Manager appearance</string>
|
||||
<string name="no_plugins_installed">No plugins installed!</string>
|
||||
<string name="store">Store</string>
|
||||
<string name="paging_initial_load">Loading…</string>
|
||||
<string name="contributor">Contributor</string>
|
||||
<string name="apply">Apply</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="download_apk">Download APK</string>
|
||||
<string name="select_apk">Select APK</string>
|
||||
<string name="download_method">Download Method</string>
|
||||
<string name="changelog_media">Changelog Media</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.AliucordManager" parent="Theme.AppCompat.NoActionBar">
|
||||
<style name="Theme.AliucordManager.SplashScreen" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.AliucordManager</item>
|
||||
</style>
|
||||
<style name="Theme.AliucordManager" parent="Theme.AppCompat">
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Add table
Reference in a new issue