Add plugins page
This commit is contained in:
parent
7881279196
commit
137a739705
19 changed files with 490 additions and 34 deletions
|
|
@ -12,12 +12,12 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdk = 30
|
||||
compileSdk = 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.aliucord.manager"
|
||||
minSdk = 24
|
||||
targetSdk = 30
|
||||
targetSdk = 29
|
||||
versionCode = 1
|
||||
versionName = "0.0.1"
|
||||
|
||||
|
|
@ -51,17 +51,17 @@ android {
|
|||
|
||||
dependencies {
|
||||
// core dependencies
|
||||
implementation("androidx.appcompat:appcompat:1.3.0")
|
||||
implementation("androidx.core:core-ktx:1.6.0")
|
||||
implementation("androidx.appcompat:appcompat:1.3.1")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
|
||||
// compose dependencies
|
||||
implementation("androidx.activity:activity-compose:1.3.0-rc02")
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
implementation("androidx.compose.ui:ui:${composeVersion}")
|
||||
implementation("androidx.compose.material:material:${composeVersion}")
|
||||
implementation("androidx.compose.ui:ui-tooling:${composeVersion}")
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha08")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.0-alpha04")
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc01")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.0-beta02")
|
||||
|
||||
// accompanist dependencies
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -98,6 +99,15 @@ fun MainActivityLayout() {
|
|||
tint = MaterialTheme.colors.onPrimary
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { navController.navigate(Screen.Plugins.route) {
|
||||
popUpTo(Screen.Home.route) { saveState = true }
|
||||
} }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_plugin_24dp),
|
||||
contentDescription = stringResource(id = R.string.launch_plugins),
|
||||
tint = MaterialTheme.colors.onPrimary
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { isMenuExpanded = !isMenuExpanded }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@
|
|||
package com.aliucord.manager.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.screens.HomeScreen
|
||||
import com.aliucord.manager.ui.screens.PluginsScreen
|
||||
import com.aliucord.manager.ui.screens.SettingsScreen
|
||||
|
||||
sealed class Screen(
|
||||
|
|
@ -18,7 +20,7 @@ sealed class Screen(
|
|||
) {
|
||||
companion object {
|
||||
// NOTE: without lazy it doesn't work, kotlin moment https://youtrack.jetbrains.com/issue/KT-8970
|
||||
val SCREENS by lazy { listOf(Home, Settings) }
|
||||
val SCREENS by lazy { listOf(Home, Settings, Plugins) }
|
||||
}
|
||||
|
||||
object Home : Screen(
|
||||
|
|
@ -32,4 +34,10 @@ sealed class Screen(
|
|||
R.string.settings,
|
||||
{ SettingsScreen() }
|
||||
)
|
||||
|
||||
object Plugins : Screen(
|
||||
"plugins",
|
||||
R.string.plugins,
|
||||
{ PluginsScreen() }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
package com.aliucord.manager.ui.components
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val hyperLinkPattern = Pattern.compile("\\[(.+?)]\\((.+?\\))")
|
||||
|
||||
@SuppressLint("ComposableNaming") // Can't use MaterialTheme without Composable, but this is not a component
|
||||
@Composable
|
||||
private fun AnnotatedString.Builder.hyperlink(content: String, outTags: ArrayList<String>) {
|
||||
var idx = 0
|
||||
with (hyperLinkPattern.matcher(content)) {
|
||||
while (find()) {
|
||||
val start = start()
|
||||
val end = end()
|
||||
val title = group(1)!!
|
||||
val url = group(2)!!
|
||||
|
||||
append(content.substring(idx, start))
|
||||
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline)) {
|
||||
append(title)
|
||||
}
|
||||
addStringAnnotation(title, url, start, end)
|
||||
idx = end + 1
|
||||
outTags.add(title)
|
||||
}
|
||||
}
|
||||
if (idx < content.length) append(content.substring(idx))
|
||||
}
|
||||
|
||||
const val bulletPoint = '•'
|
||||
@Composable
|
||||
fun Changelog(changelog: String) {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(modifier = Modifier
|
||||
.fillMaxHeight(0.7f)
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
changelog.lines().forEach {
|
||||
var line = it.trim()
|
||||
if (line.isNotEmpty()) {
|
||||
when (line[0]) {
|
||||
'#' -> {
|
||||
do {
|
||||
line = line.substring(1);
|
||||
} while (line.startsWith("#"))
|
||||
Text(line.trimStart(), style = MaterialTheme.typography.h6)
|
||||
}
|
||||
'*' -> {
|
||||
val tags = ArrayList<String>()
|
||||
LinkText(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) {
|
||||
append(bulletPoint)
|
||||
}
|
||||
hyperlink(line.substring(1), tags)
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val tags = ArrayList<String>()
|
||||
LinkText(
|
||||
buildAnnotatedString {
|
||||
hyperlink(line, tags)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LinkText(annotatedString: AnnotatedString) {
|
||||
val urlHandler = LocalUriHandler.current
|
||||
ClickableText(
|
||||
text = annotatedString,
|
||||
style = TextStyle(color = LocalContentColor.current),
|
||||
onClick = { offset ->
|
||||
annotatedString.getStringAnnotations(offset, offset).firstOrNull()?.let {
|
||||
urlHandler.openUri(it.item)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -53,7 +53,10 @@ fun ExposedDropdownMenu(
|
|||
) {
|
||||
items.forEachIndexed { i, item ->
|
||||
DropdownMenuItem(
|
||||
onClick = { selectItem(i) }
|
||||
onClick = {
|
||||
expanded = false
|
||||
selectItem(i)
|
||||
}
|
||||
) {
|
||||
Text(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@
|
|||
|
||||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
|
@ -21,21 +20,47 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.ui.components.CommitsList
|
||||
import com.aliucord.manager.utils.Github
|
||||
import com.aliucord.manager.utils.Versions
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@Preview
|
||||
fun HomeScreen() {
|
||||
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
|
||||
val selectedCommit = remember { mutableStateOf<String?>(null) }
|
||||
val versions = remember { mutableStateOf<Versions?>(null) }
|
||||
|
||||
if (versions.value == null) {
|
||||
LaunchedEffect(null) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
versions.value = Github.versions
|
||||
} catch (th: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to get versions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Card(modifier = Modifier.padding(bottom = 8.dp)) {
|
||||
ListItem(
|
||||
text = { Text("Aliucord") },
|
||||
secondaryText = { Text(buildAnnotatedString {
|
||||
append("Supported version: ")
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append(selectedCommit.value ?: "blah") }
|
||||
}) },
|
||||
secondaryText = {
|
||||
Text(buildAnnotatedString {
|
||||
append("Supported version: ")
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(
|
||||
versions.value?.versionName ?: "?"
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
package com.aliucord.manager.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.Changelog
|
||||
import com.aliucord.manager.utils.Plugin
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@Preview
|
||||
fun PluginsScreen() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(12.dp)
|
||||
) {
|
||||
var plugins by remember { mutableStateOf(Plugin.loadAll()) }
|
||||
var search by remember { mutableStateOf("") }
|
||||
|
||||
TextField(
|
||||
value = search,
|
||||
placeholder = { Text(stringResource(R.string.search)) },
|
||||
onValueChange = { search = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
items(plugins.filter {
|
||||
it.manifest.run {
|
||||
name.contains(search, true)
|
||||
|| description.contains(search, true)
|
||||
|| authors.any { a -> a.name.contains(search, true) }
|
||||
}
|
||||
}) { p ->
|
||||
// TODO - make this get pluginEnabled from Aliucord preferences somehow
|
||||
val isChecked = remember { mutableStateOf(true) }
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
var showChangelogDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDeleteDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteDialog = false },
|
||||
title = { Text(stringResource(R.string.delete_plugin, p.manifest.name)) },
|
||||
text = { Text(stringResource(R.string.delete_plugin_body)) },
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showDeleteDialog = false
|
||||
p.file.delete()
|
||||
plugins = plugins.filter { it != p }
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.error, contentColor = MaterialTheme.colors.onError)
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok).uppercase())
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showDeleteDialog = false
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary)
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel).uppercase())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showChangelogDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showChangelogDialog = false },
|
||||
title = { Text(stringResource(R.string.changelog, p.manifest.name), style = MaterialTheme.typography.h5) },
|
||||
text = {
|
||||
Changelog(p.manifest.changelog!!)
|
||||
},
|
||||
confirmButton = {},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showChangelogDialog = false
|
||||
},
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel).uppercase())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
elevation = 1.dp
|
||||
) {
|
||||
Column {
|
||||
ListItem(
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.plugin_title,
|
||||
p.manifest.name,
|
||||
p.manifest.version,
|
||||
p.manifest.authors.joinToString(
|
||||
" ",
|
||||
transform = { it.name })
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = isChecked.value,
|
||||
onCheckedChange = { isChecked.value = it },
|
||||
)
|
||||
},
|
||||
)
|
||||
Divider(color = MaterialTheme.colors.primary)
|
||||
Text(
|
||||
p.manifest.description,
|
||||
modifier = Modifier
|
||||
.heightIn(min = 50.dp)
|
||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 0.dp)
|
||||
)
|
||||
|
||||
Row(modifier = Modifier.align(Alignment.End)) {
|
||||
if (p.manifest.changelog != null) {
|
||||
IconButton(onClick = { showChangelogDialog = true }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_history_white_24dp),
|
||||
contentDescription = stringResource(
|
||||
R.string.view_plugin_changelog,
|
||||
p.manifest.name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { showDeleteDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(
|
||||
R.string.delete_plugin,
|
||||
p.manifest.name
|
||||
),
|
||||
tint = MaterialTheme.colors.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -23,7 +25,7 @@ import com.aliucord.manager.preferences.themePreference
|
|||
import com.aliucord.manager.preferences.useDexFromStoragePreference
|
||||
import com.aliucord.manager.ui.components.ExposedDropdownMenu
|
||||
|
||||
private val themes = arrayOf("System", "Light", "Dark")
|
||||
private val themes = arrayOf("System", "Light", "Dark", "Black")
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@ val darkBackground = Color(0xff2e2e2e)
|
|||
val primaryColor = Color(0xff00c853)
|
||||
val primaryColorLight = Color(0xff5efc82)
|
||||
val primaryColorDark = Color(0xff009624)
|
||||
val errorColor = Color(0xffed4245)
|
||||
|
|
@ -13,20 +13,33 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import com.aliucord.manager.preferences.themePreference
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
primary = primaryColor,
|
||||
primaryVariant = primaryColorDark,
|
||||
secondary = primaryColorLight,
|
||||
error = errorColor,
|
||||
)
|
||||
|
||||
private val DarkColorPalette = darkColors(
|
||||
primary = primaryColor,
|
||||
primaryVariant = primaryColorDark,
|
||||
secondary = primaryColorLight,
|
||||
error = errorColor,
|
||||
|
||||
onPrimary = Color.White,
|
||||
background = darkBackground,
|
||||
surface = Color(0xff424242)
|
||||
surface = Color(0xff424242),
|
||||
)
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
private val BlackColorPalette = darkColors(
|
||||
primary = primaryColor,
|
||||
primaryVariant = primaryColorDark,
|
||||
secondary = primaryColorLight
|
||||
secondary = primaryColorLight,
|
||||
error = errorColor,
|
||||
|
||||
onPrimary = Color.White,
|
||||
background = Color(0xff000000),
|
||||
surface = Color(0xff121212)
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -36,9 +49,15 @@ fun isDark() = when (themePreference.value.value) {
|
|||
else -> true
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getTheme() = if (!isDark()) LightColorPalette else when (themePreference.value.value) {
|
||||
3 -> BlackColorPalette
|
||||
else -> DarkColorPalette
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AliucordManagerTheme(content: @Composable () -> Unit) {
|
||||
val colors = if (isDark()) DarkColorPalette else LightColorPalette
|
||||
val colors = getTheme()
|
||||
|
||||
MaterialTheme(
|
||||
colors,
|
||||
|
|
|
|||
11
app/src/main/java/com/aliucord/manager/utils/Constants.kt
Normal file
11
app/src/main/java/com/aliucord/manager/utils/Constants.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.aliucord.manager.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import com.google.gson.Gson
|
||||
import java.io.File
|
||||
|
||||
val aliucordDir = File(Environment.getExternalStorageDirectory(), "Aliucord")
|
||||
val pluginsDir = File(aliucordDir, "plugins")
|
||||
|
||||
val gson = Gson()
|
||||
|
|
@ -11,16 +11,23 @@ import com.google.gson.reflect.TypeToken
|
|||
import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
|
||||
data class Versions(val versionCode: String, val versionName: String, val aliucordHash: String)
|
||||
|
||||
object Github {
|
||||
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 commitsUrl = "https://api.github.com/repos/$org/$repo/commits"
|
||||
private const val dataUrl = "https://raw.githubusercontent.com/$org/$repo/builds/data.json"
|
||||
|
||||
fun checkForUpdates() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
val versions: Versions by lazy {
|
||||
gson.fromJson(InputStreamReader(URL(dataUrl).openStream()), Versions::class.java)
|
||||
}
|
||||
|
||||
private val commitsCache = HashMap<String, Array<Commit>>()
|
||||
private val commitsType = object : TypeToken<Array<Commit>>() {}.type
|
||||
private val gson = Gson()
|
||||
|
|
|
|||
59
app/src/main/java/com/aliucord/manager/utils/Plugin.kt
Normal file
59
app/src/main/java/com/aliucord/manager/utils/Plugin.kt
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package com.aliucord.manager.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.ZipException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
data class Manifest(
|
||||
val name: String,
|
||||
val authors: Array<Author>,
|
||||
val description: String,
|
||||
val version: String,
|
||||
val changelog: String?,
|
||||
val changelogMedia: String?
|
||||
) {
|
||||
data class Author(val name: String, val id: Long)
|
||||
}
|
||||
|
||||
class Plugin(val file: File) {
|
||||
val manifest: Manifest
|
||||
|
||||
init {
|
||||
ZipFile(file).use {
|
||||
val entry = it.getEntry("manifest.json")
|
||||
?: throw ZipException("Plugin ${file.nameWithoutExtension} has no manifest.")
|
||||
it.getInputStream(entry).use { zis ->
|
||||
manifest = gson.fromJson(InputStreamReader(zis), Manifest::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun loadAll(): List<Plugin> {
|
||||
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
|
||||
Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val files = pluginsDir.listFiles() ?: return emptyList()
|
||||
val plugins = ArrayList<Plugin>(files.size)
|
||||
|
||||
files.forEach {
|
||||
if (!it.name.endsWith(".zip")) {
|
||||
Log.w(BuildConfig.TAG, "Found non zip ${it.name} in plugins folder.")
|
||||
} else {
|
||||
try {
|
||||
plugins.add(Plugin(it))
|
||||
} catch (th: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to load plugin ${it.nameWithoutExtension}", th)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plugins.apply { sortBy { it.manifest.name }}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
app/src/main/res/drawable/ic_history_white_24dp.xml
Normal file
3
app/src/main/res/drawable/ic_history_white_24dp.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#ffffff" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_plugin_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_plugin_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10.5,4.5c0.28,0 0.5,0.22 0.5,0.5v2h6v6h2c0.28,0 0.5,0.22 0.5,0.5s-0.22,0.5 -0.5,0.5h-2v6h-2.12c-0.68,-1.75 -2.39,-3 -4.38,-3s-3.7,1.25 -4.38,3H4v-2.12c1.75,-0.68 3,-2.39 3,-4.38 0,-1.99 -1.24,-3.7 -2.99,-4.38L4,7h6V5c0,-0.28 0.22,-0.5 0.5,-0.5m0,-2C9.12,2.5 8,3.62 8,5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8h0.29c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-0.3c0,-1.49 1.21,-2.7 2.7,-2.7s2.7,1.21 2.7,2.7v0.3H17c1.1,0 2,-0.9 2,-2v-4c1.38,0 2.5,-1.12 2.5,-2.5S20.38,11 19,11V7c0,-1.1 -0.9,-2 -2,-2h-4c0,-1.38 -1.12,-2.5 -2.5,-2.5z" />
|
||||
</vector>
|
||||
25
app/src/main/res/layout/plugin.xml
Normal file
25
app/src/main/res/layout/plugin.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/plugin_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/design_default_color_secondary_variant"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plugin_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="@color/design_default_color_primary"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/plugin_header" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -11,4 +11,13 @@
|
|||
<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 by %3$s</string>
|
||||
<string name="delete_plugin">Delete %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 delete this plugin? This action cannot be undone.</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="changelog">%1$s Changelog</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
## For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
#
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
#
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
#Wed Nov 10 16:11:30 CET 2021
|
||||
kotlin.code.style=official
|
||||
org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -Dfile.encoding\=UTF-8
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
Loading…
Add table
Reference in a new issue