Overhauled UI and refactoring

This commit is contained in:
litleck 2022-02-17 18:25:22 -05:00
parent b05ae80894
commit f0cd29c500
55 changed files with 1642 additions and 1065 deletions

View file

@ -3,8 +3,9 @@
* Licensed under the Open Software License version 3.0
*/
val accompanistVersion = "0.14.0"
val composeVersion = "1.0.0-rc02"
val accompanistVersion = "0.23.0"
val composeVersion = "1.1.0"
val ktorVersion = "1.6.7"
plugins {
id("com.android.application")
@ -17,7 +18,7 @@ android {
defaultConfig {
applicationId = "com.aliucord.manager"
minSdk = 24
targetSdk = 29
targetSdk = 31
versionCode = 1
versionName = "0.0.1"
@ -33,42 +34,46 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = composeVersion
}
buildFeatures.compose = true
composeOptions.kotlinCompilerExtensionVersion = composeVersion
}
dependencies {
// core dependencies
implementation("androidx.appcompat:appcompat:1.4.0")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.core:core-ktx:1.7.0")
implementation("com.google.android.material:material:1.4.0")
implementation("com.google.android.material:material:1.5.0")
// compose dependencies
implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.compose.ui:ui:${composeVersion}")
implementation("androidx.compose.material3:material3:1.0.0-alpha05")
implementation("androidx.compose.material:material:${composeVersion}")
implementation("androidx.compose.ui:ui-tooling:${composeVersion}")
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc02")
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
implementation("androidx.compose.ui:ui-tooling-preview:1.1.0")
implementation("androidx.navigation:navigation-compose:2.4.1")
// accompanist dependencies
implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}")
implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}")
// other dependencies
implementation("com.google.code.gson:gson:2.8.7")
implementation("com.google.code.gson:gson:2.8.9")
implementation("io.coil-kt:coil-compose:1.4.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
}
// ktor
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-android:$ktorVersion")
}

View file

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:testOnly="false"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@ -30,8 +31,7 @@
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:label="@string/app_name">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -39,4 +39,4 @@
</intent-filter>
</activity>
</application>
</manifest>
</manifest>

View file

@ -0,0 +1,20 @@
package com.aliucord.manager.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
class StorePreferences(private val context: Context) {
private val THEME = intPreferencesKey("theme")
companion object {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("settings")
}
init {
}
}

View file

@ -7,7 +7,7 @@ package com.aliucord.manager.models
import com.google.gson.annotations.SerializedName
class Commit(
data class Commit(
@SerializedName("html_url") val htmlUrl: String,
val sha: String,
val commit: Commit,

View file

@ -1,3 +1,8 @@
package com.aliucord.manager.models
data class GithubUser(val login: String, val contributions: Int)
import com.google.gson.annotations.SerializedName
data class GithubUser(
@SerializedName("login") val name: String,
val contributions: Int
)

View file

@ -7,6 +7,7 @@ package com.aliucord.manager.preferences
import android.content.SharedPreferences
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.edit
import kotlin.reflect.KProperty
var sharedPreferences: SharedPreferences? = null
@ -26,12 +27,11 @@ class Preference<T>(
fun get() = value.value
fun set(newValue: T) {
if (value.value != newValue) {
value.value = newValue
val editor = sharedPreferences!!.edit()
editor.setter(key, newValue)
editor.apply()
}
if (value.value == newValue) return
value.value = newValue
sharedPreferences!!.edit { setter(key, newValue) }
}
}
@ -63,4 +63,4 @@ fun stringPreference(
defaultValue,
SharedPreferences::getString,
SharedPreferences.Editor::putString
)
)

View file

@ -7,9 +7,13 @@ package com.aliucord.manager.preferences
import android.os.Environment
val themePreference = intPreference("theme")
val replaceBgPreference = boolPreference("replace_bg", true)
var themePref = intPreference("theme")
val replaceBg = boolPreference("replace_bg", true)
val devModePreference = boolPreference("dev_mode", false)
val debuggablePreference = boolPreference("debuggable", false)
val debuggablePreference = boolPreference("debuggable")
val useDexFromStoragePreference = boolPreference("use_dex_from_storage")
val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Aliucord.dex")
val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Injector.dex")
object Preferences {
val useBlack = boolPreference("use_black")
}

View file

@ -7,188 +7,94 @@ package com.aliucord.manager.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.MoreVert
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.foundation.ExperimentalFoundationApi
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.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.preferences.sharedPreferences
import com.aliucord.manager.ui.theme.AliucordManagerTheme
import com.aliucord.manager.ui.theme.primaryColor
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.theme.ManagerTheme
import com.aliucord.manager.utils.Github
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionRequired
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@ExperimentalMaterialApi
@ExperimentalFoundationApi
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE)
sharedPreferences = getPreferences(Context.MODE_PRIVATE)
Github.checkForUpdates()
setContent {
AliucordManagerTheme {
ManagerTheme {
MainActivityLayout()
}
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@ExperimentalMaterialApi
@ExperimentalMaterial3Api
@ExperimentalFoundationApi
@ExperimentalPermissionsApi
@Composable
fun MainActivityLayout() {
val systemUiController = rememberSystemUiController()
private fun MainActivityLayout() {
val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE)
val navController = rememberNavController()
val colors = MaterialTheme.colors
SideEffect {
systemUiController.setNavigationBarColor(colors.background)
systemUiController.setStatusBarColor(colors.primary)
if (!storagePermissionState.hasPermission) storagePermissionState.launchPermissionRequest()
if (!storagePermissionState.hasPermission) SideEffect {
storagePermissionState.launchPermissionRequest()
}
val navController = rememberNavController()
var isMenuExpanded by remember { mutableStateOf(false) }
Scaffold(
topBar = {
val route = navController.currentBackStackEntryAsState().value?.destination?.route ?: Screen.Home.route
TopAppBar(
title = { Text(stringResource(Screen.SCREENS.find { it.route == route }!!.displayName)) },
backgroundColor = primaryColor,
contentColor = MaterialTheme.colors.onPrimary,
navigationIcon = if (route != Screen.Home.route) {{
IconButton(onClick = { navController.popBackStack() }) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colors.onPrimary
)
}
}} else null,
actions = {
if (route != Screen.Home.route) return@TopAppBar
val context = LocalContext.current
IconButton(
onClick = { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://discord.gg/${BuildConfig.SUPPORT_SERVER}"))) },
) {
Icon(
painter = painterResource(R.drawable.ic_discord),
contentDescription = stringResource(R.string.support_server),
tint = MaterialTheme.colors.onPrimary
)
}
IconButton(
onClick = {
navController.navigate(Screen.Plugins.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
},
modifier = Modifier.padding(bottom = 2.dp, start = 8.dp) // Make up for icon being off center
) {
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,
contentDescription = stringResource(R.string.show_menu),
tint = MaterialTheme.colors.onPrimary
)
}
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
DropdownMenuItem(onClick = {
isMenuExpanded = false
navController.navigate(Screen.About.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
}) {
Text(stringResource(Screen.About.displayName))
}
DropdownMenuItem(onClick = {
isMenuExpanded = false
navController.navigate(Screen.Settings.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
}) {
Text(stringResource(Screen.Settings.displayName))
}
}
}
)
}
) {
topBar = { AppBar(navController) }
) { paddingValues ->
PermissionRequired(
permissionState = storagePermissionState,
permissionNotGrantedContent = { GrantPermission(permissionState = storagePermissionState) },
permissionNotAvailableContent = { GrantPermission(permissionState = storagePermissionState) }
permissionNotGrantedContent = { GrantPermission(storagePermissionState) },
permissionNotAvailableContent = { GrantPermission(storagePermissionState) }
) {
NavHost(
navController,
modifier = Modifier.padding(paddingValues).padding(12.dp),
startDestination = Screen.Home.route
) {
for (screen in Screen.SCREENS) {
composable(screen.route) { screen.content() }
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(
route = Screen.Installer.route,
// arguments = listOf(
// navArgument("method") { type = NavType.EnumType( type = InstallMethod::class.java ) }
// ))
) {
InstallerScreen()
}
}
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun GrantPermission(permissionState: PermissionState) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(stringResource(R.string.permission_required), style = MaterialTheme.typography.h6, textAlign = TextAlign.Center)
Spacer(Modifier.height(8.dp))
Row {
val context = LocalContext.current
Button(onClick = { permissionState.launchPermissionRequest() }) {
Text(stringResource(R.string.permission_grant))
}
Spacer(Modifier.width(8.dp))
Button(
onClick = {
context.startActivity(Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
))
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray, contentColor = Color.Black)
) {
Text(stringResource(R.string.open_settings))
}
}
}
}
enum class InstallMethod {
DOWNLOAD, INSTALLED, STORAGE
}

View file

@ -6,45 +6,19 @@
package com.aliucord.manager.ui
import androidx.annotation.StringRes
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.foundation.ExperimentalFoundationApi
import com.aliucord.manager.R
import com.aliucord.manager.ui.screens.AboutScreen
import com.aliucord.manager.ui.screens.HomeScreen
import com.aliucord.manager.ui.screens.PluginsScreen
import com.aliucord.manager.ui.screens.SettingsScreen
sealed class Screen(
val route: String,
@StringRes val displayName: Int,
val content: @Composable () -> Unit
) {
@ExperimentalFoundationApi
sealed class Screen(val route: String, @StringRes val displayName: Int) {
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, Plugins, About) }
val screens by lazy { listOf(Home, About, Settings, Commits, Installer) }
}
object Home : Screen(
"home",
R.string.app_name,
{ HomeScreen() }
)
object About : Screen(
"About",
R.string.about,
{ AboutScreen() }
)
object Settings : Screen(
"settings",
R.string.settings,
{ SettingsScreen() }
)
object Plugins : Screen(
"plugins",
R.string.plugins,
{ PluginsScreen() }
)
}
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)
}

View file

@ -0,0 +1,110 @@
package com.aliucord.manager.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.ui.Screen
@ExperimentalFoundationApi
@Composable
fun AppBar(navController: NavController) {
val route = navController.currentBackStackEntryAsState().value?.destination?.route ?: Screen.Home.route
var isMenuExpanded by remember { mutableStateOf(false) }
MediumTopAppBar(
navigationIcon = {
if (route != Screen.Home.route) {
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))
},
actions = {
val localUriHandler = LocalUriHandler.current
IconButton(
onClick = {
navController.navigate(Screen.Settings.route)
},
) {
Icon(
painter = painterResource(R.drawable.ic_plugin_24dp),
contentDescription = stringResource(R.string.plugins),
tint = MaterialTheme.colorScheme.onBackground
)
}
IconButton(
onClick = {
localUriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}")
},
) {
Icon(
painter = painterResource(R.drawable.ic_discord),
contentDescription = stringResource(R.string.support_server),
tint = MaterialTheme.colorScheme.onBackground
)
}
Box(
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
) {
IconButton(
onClick = { isMenuExpanded = !isMenuExpanded }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.show_menu),
tint = MaterialTheme.colorScheme.onBackground
)
}
DropdownMenu(
modifier = Modifier.wrapContentWidth(),
expanded = isMenuExpanded,
onDismissRequest = { isMenuExpanded = false }
) {
DropdownMenuItem(
onClick = {
isMenuExpanded = false
navController.navigate(Screen.About.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
},
text = { Text(stringResource(Screen.About.displayName)) }
)
DropdownMenuItem(
onClick = {
isMenuExpanded = false
navController.navigate(Screen.Settings.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
},
text = { Text(stringResource(Screen.Settings.displayName)) }
)
}
}
}
)
}

View file

@ -1,132 +0,0 @@
/*
* Copyright (c) 2021 Juby210
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.ui.components
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.models.Commit
import com.aliucord.manager.ui.theme.isDark
import com.aliucord.manager.utils.Github
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private class CommitData(
val commit: Commit,
val buildSha: String?
)
@Composable
fun CommitsList(selectedCommit: MutableState<String?>, modifier: Modifier = Modifier) {
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
var isLoading by remember { mutableStateOf(true) }
val commits = remember { mutableStateListOf<CommitData>() }
if (isLoading) {
ConstraintLayout(modifier = modifier
.fillMaxSize()
.padding(12.dp)) {
CommitsHeader()
CircularProgressIndicator(modifier = Modifier.constrainAs(createRef()) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
})
}
LaunchedEffect(null) {
coroutineScope.launch {
try {
val buildCommits = Github.getCommits(mapOf("sha" to "builds", "path" to "Aliucord.dex", "per_page" to "50"))
val commits2 = Github.getCommits(mapOf("per_page" to "50"))
commits.addAll(commits2.map { c -> CommitData(c, buildCommits.find { bc -> bc.commit.message.substring(6) == c.sha }?.sha) })
isLoading = false
if (commits.size > 0) selectedCommit.value = commits.firstOrNull { it.buildSha != null }?.buildSha
} catch (e: Throwable) {
Log.e(BuildConfig.TAG, "Failed to get commits", e)
}
}
}
} else {
val dark = isDark()
// LazyColumn causes too much lags, also you can't get scrollbar on it, so I used AndroidView with RecyclerView for now
Column(
modifier = modifier
.fillMaxSize()
.padding(12.dp)
) {
CommitsHeader()
AndroidView({ context -> View.inflate(context, R.layout.recycler, null).apply {
val rv = this as RecyclerView
rv.adapter = CommitsAdapter(commits, dark)
rv.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}})
}
}
}
@Composable
private fun CommitsHeader() {
Text(
stringResource(R.string.commits),
style = MaterialTheme.typography.subtitle1,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(bottom = 6.dp)
)
}
private class CommitsAdapter(private val commits: List<CommitData>, private val dark: Boolean) : RecyclerView.Adapter<CommitsAdapter.ViewHolder>() {
class ViewHolder(view: View, dark: Boolean) : RecyclerView.ViewHolder(view) {
val sha: TextView = view.findViewById(R.id.commit_sha)
val message: TextView = view.findViewById(R.id.commit_message)
init {
message.setTextColor(if (dark) 0xccffffff.toInt() else 0xcc000000.toInt())
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.commit, parent, false), dark)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val data = commits[position]
val commit = data.commit
val color = if (data.buildSha != null) holder.sha.context.getColor(R.color.primary) else Color.GRAY
holder.sha.text = commit.sha.substring(0, 7)
holder.sha.setTextColor(color)
holder.sha.setOnClickListener { it.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(commit.htmlUrl))) }
holder.message.text = "${commit.commit.message.split("\n")[0]} - ${commit.author.name}"
}
override fun getItemCount() = commits.size
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2021 Juby210
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.ui.components
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ExperimentalMaterialApi
@Composable
fun ExposedDropdownMenu(
items: Array<String>,
selectedItem: Int,
selectItem: (item: Int) -> Unit = {},
width: Dp = 200.dp
) {
var expanded by remember { mutableStateOf(false) }
val rotateAnimation by animateFloatAsState(
targetValue = if (expanded) 180F else 0F,
animationSpec = tween(
durationMillis = 250,
easing = FastOutSlowInEasing
)
)
Column(modifier = Modifier.width(width)) {
ListItem(
text = { Text(items[selectedItem]) },
trailing = {
Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null, modifier = Modifier.rotate(rotateAnimation))
},
modifier = Modifier.clickable { expanded = !expanded }
)
if (expanded) Divider(color = MaterialTheme.colors.primary) else Divider()
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.width(width)
) {
items.forEachIndexed { i, item ->
DropdownMenuItem(
onClick = {
expanded = false
selectItem(i)
}
) {
Text(item)
}
}
}
}
}

View file

@ -0,0 +1,65 @@
package com.aliucord.manager.ui.components
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.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 com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun GrantPermission(permissionState: PermissionState) = Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
stringResource(R.string.permission_required),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(8.dp))
Row {
val context = LocalContext.current
Button(onClick = { permissionState.launchPermissionRequest() }) {
Text(stringResource(R.string.permission_grant))
}
Spacer(Modifier.width(8.dp))
Button(
onClick = {
context.startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
)
)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color.LightGray,
contentColor = Color.Black
),
) {
Text(stringResource(R.string.open_settings))
}
}
}

View file

@ -0,0 +1,121 @@
package com.aliucord.manager.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.aliucord.manager.R
import com.aliucord.manager.ui.components.plugins.Changelog
import com.aliucord.manager.ui.components.plugins.PluginCard
import com.aliucord.manager.utils.Plugin
@Composable
fun PluginsList() = Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
var plugins by remember { mutableStateOf(Plugin.loadAll()) }
val focusManager = LocalFocusManager.current
var search by remember { mutableStateOf("") }
if (plugins.isNotEmpty()) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
singleLine = true,
value = search,
onValueChange = { search = it },
placeholder = { Text(stringResource(R.string.search)) },
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),
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.colorScheme.onSecondaryContainer,
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
focusedBorderColor = MaterialTheme.colorScheme.outline,
cursorColor = MaterialTheme.colorScheme.primary
)
)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(plugins.filter { plugin ->
plugin.manifest.run {
name.contains(search, true)
|| description.contains(search, true)
|| authors.any { a -> a.name.contains(search, true) }
}
}) { plugin ->
// 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) }
if (showDeleteDialog) AlertDialog(
onDismissRequest = { showDeleteDialog = false },
title = { Text(stringResource(R.string.delete_plugin, plugin.manifest.name)) },
text = { Text(stringResource(R.string.delete_plugin_body)) },
confirmButton = {
Button(
onClick = {
showDeleteDialog = false
plugin.file.delete()
plugins = plugins.filter { it != plugin }
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
) {
Text(stringResource(android.R.string.ok).uppercase())
}
},
dismissButton = {
Button(
onClick = {
showDeleteDialog = false
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Text(stringResource(android.R.string.cancel).uppercase())
}
}
)
if (showChangelogDialog) Dialog(
onDismissRequest = { showChangelogDialog = false },
content = { Changelog(plugin, onDismiss = { showChangelogDialog = false }) }
)
PluginCard(
plugin = plugin,
onDelete = { showDeleteDialog = true },
onShowChangelog = { showChangelogDialog = true }
)
}
}
} else {
Text(stringResource(R.string.no_plugins_installed))
}
}

View file

@ -0,0 +1,34 @@
package com.aliucord.manager.ui.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material.SwitchDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.material.Switch as M2Switch
@Composable
fun Switch(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val colors = MaterialTheme.colorScheme
M2Switch(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
interactionSource = interactionSource,
colors = SwitchDefaults.colors(
checkedTrackColor = colors.surfaceVariant,
uncheckedTrackColor = colors.secondaryContainer,
checkedThumbColor = colors.primary,
uncheckedThumbColor = colors.onSecondaryContainer
)
)
}

View file

@ -0,0 +1,29 @@
package com.aliucord.manager.ui.components.about
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
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.unit.dp
import coil.compose.rememberImagePainter
import coil.transform.CircleCropTransformation
@Composable
fun ContributorEntry(name: String) {
val uriHandler = LocalUriHandler.current
Image(
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") }
)
}

View file

@ -0,0 +1,65 @@
package com.aliucord.manager.ui.components.about
import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.models.GithubUser
import com.aliucord.manager.utils.Github
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@ExperimentalFoundationApi
@Composable
fun ContributorsCard() = ElevatedCard(
modifier = Modifier.padding(bottom = 8.dp).wrapContentHeight().fillMaxWidth()
) {
val contributors = remember { mutableStateOf<Array<GithubUser>?>(null) }
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
stringResource(R.string.contributors),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
if (contributors.value != null) LazyVerticalGrid(
cells = GridCells.Adaptive(48.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
items(contributors.value!!) { user -> ContributorEntry(user.name) }
} else {
Text(stringResource(R.string.loading))
LaunchedEffect(null) {
launch(Dispatchers.IO) {
try {
contributors.value = Github.contributors
} catch (err: Throwable) {
Log.e(BuildConfig.TAG, "Failed to load contributors", err)
}
}
}
}
}
}

View file

@ -0,0 +1,35 @@
package com.aliucord.manager.ui.components.about
import androidx.compose.foundation.layout.*
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliucord.manager.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TeamCard() = ElevatedCard(
modifier = Modifier.padding(bottom = 8.dp).wrapContentHeight().fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.team),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Column {
UserEntry("Juby210", "Fox")
UserEntry("Vendicated", "the ven")
UserEntry("mwittrien", "your memory gon")
}
}
}

View file

@ -0,0 +1,40 @@
package com.aliucord.manager.ui.components.about
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import coil.transform.CircleCropTransformation
@Composable
fun UserEntry(name: String, roles: String) = Column(
modifier = Modifier.padding(vertical = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Image(
painter = rememberImagePainter(
data = "https://github.com/$name.png",
builder = {
transformations(CircleCropTransformation())
}
),
contentDescription = null,
modifier = Modifier.size(54.dp)
)
Column {
Text(
name,
style = MaterialTheme.typography.titleLarge
)
Text(roles)
}
}
}

View file

@ -0,0 +1,72 @@
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.aliucord.manager.R
import com.aliucord.manager.ui.Screen
@OptIn(ExperimentalFoundationApi::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()
Button(
modifier = modifier,
onClick = {
navController.currentBackStackEntry?.arguments
navController.navigate(Screen.Installer.route)
}
) {
Text("Download")
}
Button(
modifier = modifier,
onClick = {
navController.navigate(Screen.Installer.route)
}
) {
Text("From installed app")
}
Button(
modifier = modifier,
onClick = {
navController.navigate(Screen.Installer.route)
}
) {
Text("From storage")
}
}
},
onDismissRequest = { visible.value = false }
)

View file

@ -1,4 +1,4 @@
package com.aliucord.manager.ui.components
package com.aliucord.manager.ui.components.plugins
import android.annotation.SuppressLint
import android.graphics.BitmapFactory
@ -9,8 +9,11 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
@ -23,23 +26,23 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.ui.theme.discordBrand
import com.aliucord.manager.ui.theme.discordGreen
import com.aliucord.manager.ui.theme.discordRed
import com.aliucord.manager.utils.Plugin
import java.net.URL
import java.util.regex.Pattern
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.URL
import java.util.regex.Pattern
private val hyperLinkPattern = Pattern.compile("\\[(.+?)]\\((.+?\\))")
@Suppress("RegExpRedundantEscape") // It is very much not redundant and causes a crash lol
private val headerStylePattern = Pattern.compile("\\{(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.matcher(content)) {
while (find()) {
val start = start()
val end = end()
@ -47,7 +50,12 @@ private fun AnnotatedString.Builder.hyperlink(content: String) {
val url = group(2)!!
append(content.substring(idx, start))
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline)) {
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
)
) {
pushStringAnnotation(title, url)
append(title)
pop()
@ -55,45 +63,49 @@ private fun AnnotatedString.Builder.hyperlink(content: String) {
idx = end
}
}
if (idx < content.length) append(content.substring(idx))
}
const val bulletPoint = "● "
@Composable
fun Changelog(plugin: Plugin, showDialog: MutableState<Boolean>) {
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
fun Changelog(plugin: Plugin, onDismiss: () -> Unit) {
val imageBitmap = remember {
mutableStateOf<ImageBitmap?>(null)
}
if (imageBitmap.value == null && plugin.manifest.changelogMedia != null) {
LaunchedEffect(null) {
coroutineScope.launch {
launch(Dispatchers.IO) {
try {
imageBitmap.value = URL(plugin.manifest.changelogMedia).openStream().use {
BitmapFactory.decodeStream(it)
@Suppress("BlockingMethodInNonBlockingContext")
imageBitmap.value = URL(plugin.manifest.changelogMedia).openStream().use { stream ->
BitmapFactory.decodeStream(stream)
}.asImageBitmap()
} catch (err: Throwable) {
Log.e(BuildConfig.TAG, "Failed to load changelogMedia ${plugin.manifest.changelogMedia}", err)
Log.e(
BuildConfig.TAG,
"Failed to load changelogMedia ${plugin.manifest.changelogMedia}",
err
)
}
}
}
}
Column(
modifier = Modifier
.background(MaterialTheme.colors.surface)
.background(MaterialTheme.colorScheme.surface)
.fillMaxHeight(0.9f)
.padding(16.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Text(
plugin.manifest.name,
style = MaterialTheme.typography.h5
)
Divider(color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 8.dp))
Text(plugin.manifest.name, style = MaterialTheme.typography.headlineLarge)
Divider(color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(vertical = 8.dp))
imageBitmap.value?.let {
Image(it, "", modifier = Modifier.fillMaxWidth())
}
LazyColumn(
modifier = Modifier
.padding(bottom = 16.dp)
@ -105,17 +117,27 @@ fun Changelog(plugin: Plugin, showDialog: MutableState<Boolean>) {
when (line[0]) {
'#' -> {
do {
line = line.substring(1);
line = line.substring(1)
} while (line.startsWith("#"))
Text(line.trimStart(), fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 16.dp, bottom = 6.dp))
Text(
line.trimStart(),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
'*' -> {
LinkText(
buildAnnotatedString {
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary, fontWeight = FontWeight.Bold)) {
append(bulletPoint)
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
) {
append("● ")
}
hyperlink(line.substring(1))
},
Modifier.padding(bottom = 2.dp)
@ -124,19 +146,13 @@ fun Changelog(plugin: Plugin, showDialog: MutableState<Boolean>) {
else -> {
when {
line.endsWith("marginTop}") -> {
val color = headerStylePattern.matcher(line).run {
val category = if (find()) {
line = this.replaceFirst("")
group(1)
} else null
when (category) {
"improved" -> discordBrand
"added" -> discordGreen
"fixed" -> discordRed
else -> MaterialTheme.colors.onSurface
}
}
Text(line, fontWeight = FontWeight.Bold, color = color, modifier = Modifier.padding(top = 16.dp, bottom = 6.dp))
val color = MaterialTheme.colorScheme.onSurface
Text(
line,
fontWeight = FontWeight.Bold,
color = color,
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
line.all { c -> c == '=' } -> {} // Discord ignores =======
else -> {
@ -152,7 +168,11 @@ fun Changelog(plugin: Plugin, showDialog: MutableState<Boolean>) {
}
}
}
Button(onClick = { showDialog.value = false }, modifier = Modifier.align(Alignment.End)) {
Button(
onClick = onDismiss,
modifier = Modifier.align(Alignment.End).height(IntrinsicSize.Min)
) {
Text(stringResource(R.string.close))
}
}
@ -162,6 +182,7 @@ fun Changelog(plugin: Plugin, showDialog: MutableState<Boolean>) {
@Composable
private fun LinkText(annotatedString: AnnotatedString, modifier: Modifier = Modifier) {
val urlHandler = LocalUriHandler.current
ClickableText(
text = annotatedString,
style = TextStyle(color = LocalContentColor.current),

View file

@ -0,0 +1,112 @@
package com.aliucord.manager.ui.components.plugins
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
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 com.aliucord.manager.R
import com.aliucord.manager.ui.components.Switch
import com.aliucord.manager.utils.Plugin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PluginCard(
plugin: Plugin,
onDelete: () -> Unit,
onShowChangelog: () -> Unit
) {
var isEnabled by remember { mutableStateOf(true) }
ElevatedCard(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(12.dp)
) {
// Header
Row {
Column {
// Name
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(plugin.manifest.name)
}
append(" v${plugin.manifest.version}")
}
)
// Author(s)
Text(
"By ${plugin.manifest.authors.joinToString { it.name }}",
style = MaterialTheme.typography.labelMedium
)
}
Spacer(Modifier.weight(1f, true))
// Toggle Switch
Switch(
checked = isEnabled,
onCheckedChange = { isEnabled = it }
)
}
// Description
Text(
text = plugin.manifest.description,
modifier = Modifier.heightIn(min = 50.dp),
style = MaterialTheme.typography.bodyMedium
)
// Buttons
Row(Modifier.fillMaxWidth()) {
val uriHandler = LocalUriHandler.current
IconButton(
onClick = {
uriHandler.openUri(
plugin.manifest.updateUrl
.replace("raw.githubusercontent.com", "github.com")
.replaceFirst("/builds.*".toRegex(), "")
)
}
) {
Icon(painterResource(R.drawable.ic_account_github_white_24dp), "GitHub")
}
if (plugin.manifest.changelog != null) {
IconButton(
onClick = onShowChangelog
) {
Icon(
painter = painterResource(R.drawable.ic_history_white_24dp),
contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name)
)
}
}
Spacer(Modifier.weight(1f, true))
IconButton(onClick = onDelete) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = stringResource(R.string.delete_plugin, plugin.manifest.name),
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
}

View file

@ -0,0 +1,16 @@
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
)

View file

@ -0,0 +1,28 @@
package com.aliucord.manager.ui.components.settings
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.res.stringResource
import com.aliucord.manager.R
import com.aliucord.manager.preferences.themePref
import com.aliucord.manager.ui.theme.Theme
@ExperimentalMaterialApi
@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)
}
}
)
}

View file

@ -1,136 +1,44 @@
package com.aliucord.manager.ui.screens
import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalUriHandler
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 coil.compose.rememberImagePainter
import coil.transform.CircleCropTransformation
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.models.GithubUser
import com.aliucord.manager.utils.Github
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.aliucord.manager.ui.components.about.ContributorsCard
import com.aliucord.manager.ui.components.about.TeamCard
@ExperimentalFoundationApi
@OptIn(ExperimentalMaterialApi::class)
@Composable
@Preview
fun AboutScreen() {
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
val modifier = Modifier.padding(8.dp)
val contributors = remember { mutableStateOf<Array<GithubUser>?>(null) }
val uriHandler = LocalUriHandler.current
Column(modifier = Modifier.padding(16.dp)) {
Column(
Column {
TeamCard()
ContributorsCard()
Spacer(Modifier.weight(1f, true))
Button(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
onClick = { uriHandler.openUri("https://github.com/Aliucord/Aliucord") }
) {
Text(
stringResource(R.string.team),
style = MaterialTheme.typography.h4,
Icon(
painter = painterResource(R.drawable.ic_account_github_white_24dp),
contentDescription = "GitHub",
modifier = Modifier.padding(8.dp)
)
UserEntry("Juby210", "Fox")
UserEntry("torvalds", "Epic")
UserEntry("cyyynthia", "PSA: Uninstall Client mods by Cynthia")
}
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
stringResource(R.string.contributors),
style = MaterialTheme.typography.h4,
)
if (contributors.value == null) {
Text(
stringResource(R.string.loading)
)
LaunchedEffect(null) {
coroutineScope.launch {
try {
contributors.value = Github.contributors
} catch (err: Throwable) {
Log.e(BuildConfig.TAG, "Failed to load contributors", err)
}
}
}
} else {
LazyVerticalGrid(
cells = GridCells.Adaptive(48.dp)
) {
items(contributors.value!!) {
ContributorEntry(it.login)
}
}
}
Text(stringResource(R.string.source_code))
}
}
}
@Composable
fun UserEntry(name: String, roles: String) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp, 8.dp), shape = RoundedCornerShape(15.dp)
) {
Column(modifier = Modifier.padding(16.dp, 8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = rememberImagePainter(
data = "https://github.com/$name.png",
builder = {
transformations(CircleCropTransformation())
}
),
contentDescription = null,
modifier = Modifier
.size(48.dp)
.padding(end = 8.dp)
)
Column {
Text(
name,
style = MaterialTheme.typography.h6,
)
Text(roles)
}
}
}
}
}
@Composable
fun ContributorEntry(name: String) {
val handler = LocalUriHandler.current
Image(
painter = rememberImagePainter(
data = "https://github.com/$name.png",
),
contentDescription = null,
modifier = Modifier
.size(48.dp)
.clickable { handler.openUri("https://github.com/$name") }
)
}
}

View file

@ -0,0 +1,95 @@
package com.aliucord.manager.ui.screens
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
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.*
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.unit.dp
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.models.Commit
import com.aliucord.manager.utils.Github
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
data class CommitData(
val commit: Commit,
val buildSha: String?
)
@OptIn(ExperimentalMaterialApi::class)
@ExperimentalMaterialApi
@Composable
fun CommitsScreen() {
var isLoading by remember { mutableStateOf(true) }
val commits = remember { mutableStateListOf<CommitData>() }
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize().padding(12.dp),
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() }
)
LaunchedEffect(null) {
launch(Dispatchers.IO) {
try {
val buildCommits = Github.getCommits(
mapOf(
"sha" to "builds",
"path" to "Aliucord.dex",
"per_page" to "50"
)
)
val commits2 = Github.getCommits(mapOf("per_page" to "50"))
commits.addAll(commits2.map { c ->
CommitData(
c,
buildCommits.find { bc -> bc.commit.message.substring(6) == c.sha }?.sha
)
})
isLoading = false
} 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}") },
)
}
}
}
}
}

View file

@ -5,12 +5,18 @@
package com.aliucord.manager.ui.screens
import android.Manifest
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -20,60 +26,84 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
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 androidx.navigation.NavController
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.ui.components.CommitsList
import com.aliucord.manager.preferences.devModePreference
import com.aliucord.manager.ui.Screen
import com.aliucord.manager.ui.components.PluginsList
import com.aliucord.manager.ui.components.installer.InstallerDialog
import com.aliucord.manager.utils.Github
import com.aliucord.manager.utils.Versions
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@ExperimentalPermissionsApi
@ExperimentalFoundationApi
@Composable
@Preview
fun HomeScreen() {
fun HomeScreen(navController: NavController) {
val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE)
val packageManager = LocalContext.current.packageManager
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
val selectedCommit = remember { mutableStateOf<String?>(null) }
val versions = remember { mutableStateOf<Versions?>(null) }
val installedVersion = remember {
try {
packageManager.getPackageInfo("com.aliucord", 0).versionName
} catch (th: Throwable) { "-" }
} catch (th: Throwable) {
"-"
}
}
if (versions.value == null) {
LaunchedEffect(null) {
coroutineScope.launch {
try {
versions.value = Github.versions
} catch (th: Throwable) {
Log.e(BuildConfig.TAG, "Failed to get versions")
}
val showOptionsDialog = remember { mutableStateOf(false) }
if (versions.value == null) LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
try {
versions.value = Github.versions
} catch (th: Throwable) {
Log.e(BuildConfig.TAG, "Failed to get versions", th)
}
}
}
Column(modifier = Modifier.padding(8.dp)) {
Card(modifier = Modifier.padding(bottom = 8.dp)) {
Row(modifier = Modifier.padding(16.dp).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
if (showOptionsDialog.value) InstallerDialog(showOptionsDialog, navController)
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
ElevatedCard(
modifier = Modifier.wrapContentHeight()
) {
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column {
Text("Aliucord", style = MaterialTheme.typography.h5, color = MaterialTheme.colors.primary)
Text(
"Aliucord",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Text(buildAnnotatedString {
append("Supported version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(
versions.value?.versionName ?: "?"
)
append(versions.value?.versionName ?: "?")
}
append("\nInstalled version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(installedVersion)
}
})
}
val (drawable, description) = when {
installedVersion == "-" -> R.drawable.ic_download_24dp to R.string.install
installedVersion.startsWith(
@ -81,19 +111,135 @@ fun HomeScreen() {
) -> R.drawable.ic_reinstall_24dp to R.string.reinstall
else -> R.drawable.ic_update_24dp to R.string.update
}
IconButton(onClick = { /* TODO */ }) {
Icon(
painter = painterResource(drawable),
contentDescription = stringResource(description),
tint = MaterialTheme.colors.onPrimary,
modifier = Modifier
.padding(end = 8.dp)
.background(MaterialTheme.colors.primary, CircleShape)
.padding(7.dp)
)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.spacedBy(12.dp)
// ) {
//
//
// Button(
// onClick = {
// navController.navigate(Screen.Commits.route) {
// popUpTo(Screen.Home.route) { saveState = true }
// }
// },
// colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
// ) {
// Icon(
// painter = painterResource(R.drawable.ic_update_24dp),
// contentDescription = "Commits",
// tint = MaterialTheme.colorScheme.onSecondaryContainer,
// modifier = Modifier.padding(8.dp)
// )
// }
// }
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if (devModePreference.get()) {
showOptionsDialog.value = true
} else {
navController.navigate(Screen.Installer.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
}
}
) {
Icon(
painter = painterResource(drawable),
contentDescription = stringResource(description),
tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(8.dp)
)
Text(stringResource(description))
}
BoxWithConstraints {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// if (installedVersion != "-") {
if (true) {
Button(
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
onClick = { }
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = Icons.Default.Delete,
contentDescription = "Uninstall",
tint = MaterialTheme.colorScheme.onSecondaryContainer,
)
}
Button(
modifier = Modifier.weight(1f),
onClick = { },
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = Icons.Default.ExitToApp,
contentDescription = "Launch",
tint = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
Button(
modifier = Modifier.weight(1f),
onClick = {
navController.navigate(Screen.Commits.route) {
popUpTo(Screen.Home.route) { saveState = true }
}
},
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
)
if (this@BoxWithConstraints.minWidth > 20.dp) {
Text("Commits", color = MaterialTheme.colorScheme.onSecondaryContainer)
}
}
}
}
}
}
}
Card { CommitsList(selectedCommit) }
ElevatedCard(Modifier.fillMaxWidth().weight(1f)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Plugins",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Spacer(Modifier.weight(1f, true))
}
PluginsList()
}
}
}
}
}

View file

@ -0,0 +1,60 @@
package com.aliucord.manager.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material3.LinearProgressIndicator
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 com.aliucord.manager.utils.DownloadUtils
import com.aliucord.manager.utils.Github
import com.aliucord.manager.utils.aliucordDir
import kotlinx.coroutines.launch
import java.io.File
@Composable
fun InstallerScreen() {
var log by remember { mutableStateOf("") }
val context = LocalContext.current
LaunchedEffect(Unit) {
launch {
val supportedVersion = Github.versions.versionCode
log += "Checking for cached APK...\n"
val apk = File(context.cacheDir, "discord-${supportedVersion}").let { file ->
val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0)
if (file.exists() && archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@let file
log += "Downloading APK...\n"
DownloadUtils.downloadDiscordApk(context, supportedVersion)
log += "Finished downloading APK\n"
}
val manifest = File(aliucordDir, "AndroidManifest.xml").takeIf(File::exists) ?: run {
log += "Downloading manifest\n"
DownloadUtils.download(context, Github.getDownloadUrl("builds", "AndroidManifest.xml"))
}
}
}
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(log)
Spacer(Modifier.weight(1f, true))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(8.dp),
progress = 0.5f
)
}
}

View file

@ -1,157 +0,0 @@
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.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 androidx.compose.ui.window.Dialog
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) }
val showChangelogDialog = 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.value) {
Dialog(
onDismissRequest = { showChangelogDialog.value = false },
content = {
Changelog(p, showChangelogDialog)
}
)
}
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.value = 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
)
}
}
}
}
}
}
}
}

View file

@ -5,100 +5,152 @@
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.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.*
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.ExposedDropdownMenu
import com.aliucord.manager.ui.components.Switch
import com.aliucord.manager.ui.components.settings.SettingSwitch
import com.aliucord.manager.ui.components.settings.ThemeSetting
import com.aliucord.manager.ui.theme.Theme
private val themes = arrayOf("System", "Light", "Dark", "Black")
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@ExperimentalMaterialApi
@Composable
fun SettingsScreen() {
Column {
val modifier = Modifier.padding(top = 8.dp)
fun SettingsScreen() = Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
val openDialog = remember { mutableStateOf(false) }
ListItem(
text = { Text(stringResource(R.string.theme)) },
trailing = {
ExposedDropdownMenu(
items = themes,
selectedItem = themePreference.get(),
selectItem = themePreference::set
)
},
modifier = modifier
)
ListItem(
text = { Text(stringResource(R.string.replace_bg)) },
trailing = {
Checkbox(
checked = replaceBgPreference.get(),
onCheckedChange = replaceBgPreference::set
)
},
modifier = modifier.clickable { replaceBgPreference.set(!replaceBgPreference.get()) }
)
if (openDialog.value) {
var selected by remember { mutableStateOf(Theme.from(themePref.get())) }
val devModeOn = devModePreference.get()
ListItem(
text = { Text(stringResource(R.string.developer_mode)) },
trailing = {
Checkbox(
checked = devModeOn,
onCheckedChange = devModePreference::set
)
},
modifier = modifier
)
if (devModeOn) {
ListItem(
text = { Text(stringResource(R.string.debuggable)) },
trailing = {
Checkbox(
checked = debuggablePreference.get(),
onCheckedChange = debuggablePreference::set
)
},
modifier = modifier
)
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
)
val udfs = useDexFromStoragePreference.get()
ListItem(
text = { Text(stringResource(R.string.use_dex_from_storage)) },
trailing = {
Checkbox(
checked = udfs,
onCheckedChange = useDexFromStoragePreference::set
)
},
modifier = modifier.clickable { useDexFromStoragePreference.set(!useDexFromStoragePreference.get()) }
)
val color =
if (udfs) Color.Unspecified else MaterialTheme.colors.onBackground.copy(ContentAlpha.disabled)
ListItem(
text = { Text(stringResource(R.string.dex_location), color = color) },
secondaryText = { Text(dexLocationPreference.get(), color = color) },
modifier = if (udfs) modifier.clickable { /*TODO*/ } else modifier
)
}
Button(
onClick = { /*TODO*/ },
Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center)
}
Spacer(Modifier.weight(1f, true))
RadioButton(
selected = theme == selected,
onClick = { selected = theme }
)
}
}
}
},
confirmButton = {
Button(
onClick = {
openDialog.value = false
themePref.set(selected.ordinal)
}
) {
Text("Apply")
Icon(Icons.Default.Check, "Confirm")
}
}
)
}
}
ThemeSetting(openDialog)
ListItem(
text = { Text(stringResource(R.string.use_black)) },
secondaryText = { Text(stringResource(R.string.use_black_description)) },
trailing = { SettingSwitch(preference = Preferences.useBlack) }
)
Divider()
ListItem(
text = { Text(stringResource(R.string.replace_bg)) },
trailing = { SettingSwitch(preference = replaceBg) }
)
val devModeOn = devModePreference.get()
ListItem(
text = { Text(stringResource(R.string.developer_mode)) },
trailing = {
Switch(
checked = devModeOn,
onCheckedChange = devModePreference::set
)
}
)
if (devModeOn) {
ListItem(
text = { Text(stringResource(R.string.debuggable)) },
trailing = { SettingSwitch(preference = debuggablePreference) }
)
val udfs = useDexFromStoragePreference
ListItem(
text = { Text(stringResource(R.string.use_dex_from_storage)) },
trailing = { SettingSwitch(preference = udfs) }
)
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) }
)
}
Button(
modifier = Modifier.fillMaxWidth().padding(8.dp),
onClick = { /*TODO*/ }
) {
Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center)
}
}

View file

@ -0,0 +1,11 @@
package com.aliucord.manager.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
@Composable
fun StoreScreen() {
Column {
}
}

View file

@ -7,7 +7,7 @@ package com.aliucord.manager.ui.theme
import androidx.compose.ui.graphics.Color
val darkBackground = Color(0xff2e2e2e)
val darkBackground = Color(0xff1a1a1a)
val primaryColor = Color(0xff00c853)
val primaryColorLight = Color(0xff5efc82)
@ -17,4 +17,45 @@ val discordBrand = Color(0xff5865F2)
val discordGreen = Color(0xff57F287)
val discordRed = Color(0xffED4245)
val errorColor = discordRed
val errorColor = discordRed
val Blue10 = Color(0xFF000F5E)
val Blue20 = Color(0xFF001E92)
val Blue30 = Color(0xFF002ECC)
val Blue40 = Color(0xFF1546F6)
val Blue80 = Color(0xFFB8C3FF)
val Blue90 = Color(0xFFDDE1FF)
val DarkBlue10 = Color(0xFF00036B)
val DarkBlue20 = Color(0xFF000BA6)
val DarkBlue30 = Color(0xFF1026D3)
val DarkBlue40 = Color(0xFF3648EA)
val DarkBlue80 = Color(0xFFBBC2FF)
val DarkBlue90 = Color(0xFFDEE0FF)
val Yellow10 = Color(0xFF261900)
val Yellow20 = Color(0xFF402D00)
val Yellow30 = Color(0xFF5C4200)
val Yellow40 = Color(0xFF7A5900)
val Yellow80 = Color(0xFFFABD1B)
val Yellow90 = Color(0xFFFFDE9C)
val Red10 = Color(0xFF410001)
val Red20 = Color(0xFF680003)
val Red30 = Color(0xFF930006)
val Red40 = Color(0xFFBA1B1B)
val Red80 = Color(0xFFFFB4A9)
val Red90 = Color(0xFFFFDAD4)
val Grey10 = Color(0xFF191C1D)
val Grey20 = Color(0xFF2D3132)
val Grey80 = Color(0xFFC4C7C7)
val Grey90 = Color(0xFFE0E3E3)
val Grey95 = Color(0xFFEFF1F1)
val Grey99 = Color(0xFFFBFDFD)
val BlueGrey30 = Color(0xFF45464F)
val BlueGrey50 = Color(0xFF767680)
val BlueGrey60 = Color(0xFF90909A)
val BlueGrey80 = Color(0xFFC6C5D0)
val BlueGrey90 = Color(0xFFE2E1EC)

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2021 Juby210
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)

View file

@ -5,64 +5,64 @@
package com.aliucord.manager.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import com.aliucord.manager.preferences.themePreference
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.systemuicontroller.rememberSystemUiController
private val DarkColorScheme = darkColorScheme(
private val LightColorPalette = lightColors(
primary = primaryColor,
primaryVariant = primaryColorDark,
secondary = primaryColorLight,
error = errorColor,
)
private val DarkColorPalette = darkColors(
primary = primaryColor,
primaryVariant = primaryColorDark,
secondary = primaryColorLight,
error = errorColor,
private val LightColorScheme = lightColorScheme(
)
private val BlackColorPalette = darkColorScheme(
primary = Color.Black,
onPrimary = Color.White,
background = darkBackground,
surface = Color(0xff424242),
)
private val BlackColorPalette = darkColors(
primary = primaryColor,
primaryVariant = primaryColorDark,
secondary = primaryColorLight,
error = errorColor,
onPrimary = Color.White,
background = Color(0xff000000),
surface = Color(0xff121212)
)
@Composable
fun isDark() = when (themePreference.value.value) {
0 -> isSystemInDarkTheme()
1 -> false
else -> true
}
fun ManagerTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
isDynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
dynamicColor && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current)
isDarkTheme -> DarkColorScheme
else -> LightColorScheme
}
@Composable
fun getTheme() = if (!isDark()) LightColorPalette else when (themePreference.value.value) {
3 -> BlackColorPalette
else -> DarkColorPalette
}
val systemUiController = rememberSystemUiController()
@Composable
fun AliucordManagerTheme(content: @Composable () -> Unit) {
val colors = getTheme()
SideEffect {
systemUiController.setSystemBarsColor(
color = colorScheme.background,
darkIcons = !isDarkTheme
)
}
MaterialTheme(
colors,
colorScheme = colorScheme,
typography = Typography,
shapes = Shapes,
content
content = content
)
}
enum class Theme(val displayName: String) {
SYSTEM("System"),
LIGHT("Light"),
DARK("Dark");
companion object {
fun from(index: Int) = values()[index]
}
}

View file

@ -5,16 +5,6 @@
package com.aliucord.manager.ui.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.compose.material3.Typography
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
)
val Typography = Typography()

View file

@ -1,11 +1,12 @@
package com.aliucord.manager.utils
import android.content.Context
import android.os.Environment
import com.google.gson.Gson
import io.ktor.client.*
import io.ktor.client.engine.android.*
import java.io.File
val httpClient = HttpClient(Android)
val aliucordDir = File(Environment.getExternalStorageDirectory(), "Aliucord")
val pluginsDir = File(aliucordDir, "plugins")
val gson = Gson()

View file

@ -0,0 +1,51 @@
package com.aliucord.manager.utils
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.widget.Toast
import androidx.core.net.toFile
import androidx.core.net.toUri
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
object DownloadUtils {
private const val backendHost = "https://aliucord.com/"
suspend fun downloadDiscordApk(ctx: Context, version: String) = download(ctx, "$backendHost/download/discord?v=$version")
suspend fun download(ctx: Context, url: String): File {
val downloadManager = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
return suspendCoroutine { continuation ->
try {
val receiver = object : BroadcastReceiver() {
var downloadId = 0L
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
Toast.makeText(ctx, "Finished downloading", Toast.LENGTH_SHORT).show()
ctx.unregisterReceiver(this)
continuation.resume(downloadManager.getUriForDownloadedFile(id).toFile())
}
}
}
ctx.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
receiver.downloadId = DownloadManager.Request(url.toUri()).run {
setTitle("Downloading APK")
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
downloadManager.enqueue(this)
}
} finally {
Toast.makeText(ctx, "Finished downloading", Toast.LENGTH_SHORT).show()
}
}
}
}

View file

@ -1,39 +0,0 @@
package com.aliucord.manager.utils
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import java.io.File
import java.util.concurrent.CountDownLatch
object Downloader {
fun download(ctx: Context, url: String, file: File) {
val dm = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val latch = CountDownLatch(1)
with (DownloadReceiver(ctx, latch)) {
ctx.registerReceiver(this, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
downloadId = DownloadManager.Request(Uri.parse(url)).run {
setTitle("AliucordManager")
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
setDestinationUri(Uri.fromFile(file))
dm.enqueue(this)
}
}
latch.await()
}
}
class DownloadReceiver(private val ctx: Context, private val latch: CountDownLatch) : BroadcastReceiver() {
var downloadId = 0L
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
latch.countDown()
ctx.unregisterReceiver(this)
}
}
}

View file

@ -7,10 +7,10 @@ package com.aliucord.manager.utils
import com.aliucord.manager.models.Commit
import com.aliucord.manager.models.GithubUser
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.InputStreamReader
import java.net.URL
import kotlin.collections.set
data class Versions(val versionCode: String, val versionName: String, val aliucordHash: String)
@ -33,34 +33,25 @@ object Github {
private val commitsCache = HashMap<String, Array<Commit>>()
private val commitsType = object : TypeToken<Array<Commit>>() {}.type
private val contributorsType = object : TypeToken<Array<GithubUser>>() {}.type
private val gson = Gson()
fun getCommits(params: Map<String, String>?): Array<Commit> {
// return emptyArray()
val query = params?.map { it.key + "=" + it.value }?.joinToString("&", "?") ?: ""
if (commitsCache.containsKey(query)) return commitsCache[query] ?: emptyArray()
val res = InputStreamReader(URL(commitsUrl + query).openStream()).use {
gson.fromJson<Array<Commit>>(
it,
commitsType
)
gson.fromJson<Array<Commit>>(it, commitsType)
}
commitsCache[query] = res
return res
}
val contributors: Array<GithubUser> by lazy {
val contributors: Array<GithubUser> by lazy() {
InputStreamReader(URL(contributorsUrl).openStream()).use { stream ->
gson.fromJson<Array<GithubUser>>(
stream,
contributorsType
).apply {
gson.fromJson<Array<GithubUser>>(stream, contributorsType).apply {
sortByDescending { it.contributions }
}
}
}
fun getDownloadUrl(ref: String, file: String): String {
return "https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}";
}
}
fun getDownloadUrl(ref: String, file: String) = "https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}"
}

View file

@ -9,9 +9,10 @@ import java.util.zip.ZipFile
data class Manifest(
val name: String,
val authors: Array<Author>,
val authors: ArrayList<Author>,
val description: String,
val version: String,
val updateUrl: String,
val changelog: String?,
val changelogMedia: String?
) {
@ -39,21 +40,18 @@ class Plugin(val file: File) {
}
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 files.mapNotNull { file ->
if (!file.name.endsWith(".zip")) {
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
}
}
return plugins.apply { sortBy { it.manifest.name }}
}.sortedBy { it.manifest.name }
}
}
}

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="1px" android:height="1px" />
<solid android:color="#29dcddde" />
</shape>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<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="M12.0001,2C9.6237,1.9976 7.3241,2.8415 5.5135,4.3806C3.7028,5.9196 2.4994,8.0532 2.1189,10.3989C1.7384,12.7446 2.2057,15.1491 3.4371,17.1816C4.6684,19.2141 6.5833,20.7416 8.8386,21.4905C9.342,21.5826 9.52,21.2695 9.52,21.0055V19.3051C6.7269,19.9128 6.1376,17.9607 6.1376,17.9607C5.9523,17.3557 5.5586,16.8359 5.0265,16.4936C4.1241,15.8797 5.1001,15.8797 5.1001,15.8797C5.4164,15.9244 5.7185,16.0404 5.9833,16.2191C6.2481,16.3978 6.4688,16.6344 6.6287,16.911C6.7645,17.1573 6.9476,17.3743 7.1675,17.5497C7.3874,17.725 7.6398,17.8552 7.9102,17.9328C8.1805,18.0103 8.4635,18.0337 8.7429,18.0017C9.0223,17.9696 9.2927,17.8827 9.5384,17.7459C9.577,17.2409 9.7947,16.7662 10.1523,16.4076C7.9301,16.1559 5.5974,15.2965 5.5974,11.4966C5.5821,10.504 5.9492,9.5435 6.6225,8.814C6.3226,7.9509 6.3577,7.0067 6.7207,6.1682C6.7207,6.1682 7.5617,5.8981 9.4709,7.1934C11.1103,6.7433 12.8407,6.7433 14.4801,7.1934C16.3892,5.8981 17.2241,6.1682 17.2241,6.1682C17.5922,6.9978 17.6361,7.9353 17.3469,8.7956C18.0203,9.5251 18.3873,10.4856 18.3721,11.4782C18.3721,15.3211 16.0332,16.1621 13.8048,16.3892C14.0438,16.6294 14.2283,16.9181 14.3459,17.2359C14.4634,17.5536 14.5113,17.8929 14.4862,18.2308V20.9748C14.4862,21.3002 14.6643,21.5519 15.1738,21.4537C17.4034,20.685 19.2896,19.1531 20.4992,17.1286C21.7088,15.104 22.164,12.7171 21.7842,10.3895C21.4045,8.0619 20.2143,5.9434 18.424,4.4082C16.6336,2.8731 14.3583,2.0201 12.0001,2Z" android:fillType="evenOdd"/>
</vector>

View file

@ -3,11 +3,10 @@
android:height="19dp"
android:viewportWidth="71"
android:viewportHeight="55">
<group>
<clip-path
android:pathData="M0,0h71v55h-71z"/>
<path
android:pathData="M60.1045,4.8978C55.5792,2.8214 50.7265,1.2916 45.6527,0.4154C45.5603,0.3985 45.468,0.4408 45.4204,0.5253C44.7963,1.6353 44.105,3.0834 43.6209,4.2216C38.1637,3.4046 32.7345,3.4046 27.3892,4.2216C26.905,3.0581 26.1886,1.6353 25.5617,0.5253C25.5141,0.4436 25.4218,0.4013 25.3294,0.4154C20.2584,1.2888 15.4057,2.8186 10.8776,4.8978C10.8384,4.9147 10.8048,4.9429 10.7825,4.9795C1.578,18.7309 -0.9436,32.1443 0.2934,45.3914C0.299,45.4562 0.3354,45.5182 0.3858,45.5576C6.4587,50.0174 12.3413,52.7249 18.1147,54.5195C18.2071,54.5477 18.305,54.5139 18.3638,54.4378C19.7295,52.5728 20.9469,50.6063 21.9907,48.5383C22.0523,48.4172 21.9935,48.2735 21.8676,48.2256C19.9366,47.4931 18.0979,46.6 16.3292,45.5858C16.1893,45.5041 16.1781,45.304 16.3068,45.2082C16.679,44.9293 17.0513,44.6391 17.4067,44.3461C17.471,44.2926 17.5606,44.2813 17.6362,44.3151C29.2558,49.6202 41.8354,49.6202 53.3179,44.3151C53.3935,44.2785 53.4831,44.2898 53.5502,44.3433C53.9057,44.6363 54.2779,44.9293 54.6529,45.2082C54.7816,45.304 54.7732,45.5041 54.6333,45.5858C52.8646,46.6197 51.0259,47.4931 49.0921,48.2228C48.9662,48.2707 48.9102,48.4172 48.9718,48.5383C50.038,50.6034 51.2554,52.5699 52.5959,54.435C52.6519,54.5139 52.7526,54.5477 52.845,54.5195C58.6464,52.7249 64.529,50.0174 70.6019,45.5576C70.6551,45.5182 70.6887,45.459 70.6943,45.3942C72.1747,30.0791 68.2147,16.7757 60.1968,4.9823C60.1772,4.9429 60.1437,4.9147 60.1045,4.8978ZM23.7259,37.3253C20.2276,37.3253 17.3451,34.1136 17.3451,30.1693C17.3451,26.225 20.1717,23.0133 23.7259,23.0133C27.308,23.0133 30.1626,26.2532 30.1066,30.1693C30.1066,34.1136 27.28,37.3253 23.7259,37.3253ZM47.3178,37.3253C43.8196,37.3253 40.9371,34.1136 40.9371,30.1693C40.9371,26.225 43.7636,23.0133 47.3178,23.0133C50.9,23.0133 53.7545,26.2532 53.6986,30.1693C53.6986,34.1136 50.9,37.3253 47.3178,37.3253Z"
android:fillColor="#ffffff"/>
</group>
</vector>
<group>
<clip-path android:pathData="M0,0h71v55h-71z" />
<path
android:fillColor="#ffffff"
android:pathData="M60.1045,4.8978C55.5792,2.8214 50.7265,1.2916 45.6527,0.4154C45.5603,0.3985 45.468,0.4408 45.4204,0.5253C44.7963,1.6353 44.105,3.0834 43.6209,4.2216C38.1637,3.4046 32.7345,3.4046 27.3892,4.2216C26.905,3.0581 26.1886,1.6353 25.5617,0.5253C25.5141,0.4436 25.4218,0.4013 25.3294,0.4154C20.2584,1.2888 15.4057,2.8186 10.8776,4.8978C10.8384,4.9147 10.8048,4.9429 10.7825,4.9795C1.578,18.7309 -0.9436,32.1443 0.2934,45.3914C0.299,45.4562 0.3354,45.5182 0.3858,45.5576C6.4587,50.0174 12.3413,52.7249 18.1147,54.5195C18.2071,54.5477 18.305,54.5139 18.3638,54.4378C19.7295,52.5728 20.9469,50.6063 21.9907,48.5383C22.0523,48.4172 21.9935,48.2735 21.8676,48.2256C19.9366,47.4931 18.0979,46.6 16.3292,45.5858C16.1893,45.5041 16.1781,45.304 16.3068,45.2082C16.679,44.9293 17.0513,44.6391 17.4067,44.3461C17.471,44.2926 17.5606,44.2813 17.6362,44.3151C29.2558,49.6202 41.8354,49.6202 53.3179,44.3151C53.3935,44.2785 53.4831,44.2898 53.5502,44.3433C53.9057,44.6363 54.2779,44.9293 54.6529,45.2082C54.7816,45.304 54.7732,45.5041 54.6333,45.5858C52.8646,46.6197 51.0259,47.4931 49.0921,48.2228C48.9662,48.2707 48.9102,48.4172 48.9718,48.5383C50.038,50.6034 51.2554,52.5699 52.5959,54.435C52.6519,54.5139 52.7526,54.5477 52.845,54.5195C58.6464,52.7249 64.529,50.0174 70.6019,45.5576C70.6551,45.5182 70.6887,45.459 70.6943,45.3942C72.1747,30.0791 68.2147,16.7757 60.1968,4.9823C60.1772,4.9429 60.1437,4.9147 60.1045,4.8978ZM23.7259,37.3253C20.2276,37.3253 17.3451,34.1136 17.3451,30.1693C17.3451,26.225 20.1717,23.0133 23.7259,23.0133C27.308,23.0133 30.1626,26.2532 30.1066,30.1693C30.1066,34.1136 27.28,37.3253 23.7259,37.3253ZM47.3178,37.3253C43.8196,37.3253 40.9371,34.1136 40.9371,30.1693C40.9371,26.225 43.7636,23.0133 47.3178,23.0133C50.9,23.0133 53.7545,26.2532 53.6986,30.1693C53.6986,34.1136 50.9,37.3253 47.3178,37.3253Z" />
</group>
</vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
</vector>
</vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:height="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>
</vector>

View file

@ -3,19 +3,20 @@
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.12007617"
android:scaleY="0.12007617"
android:translateX="23.2605"
android:translateY="23.2605">
<path
android:pathData="M310.423,321.724C290.671,371.359 209.633,359.204 209.633,316.152V192.57C209.633,147.999 289.658,137.869 313.969,182.44L387.916,152.557C359.046,88.74 305.865,70 261.801,70C189.88,70 118.972,111.532 118.972,192.57V316.152C118.972,397.696 189.88,438.722 260.281,438.722C305.359,438.722 359.553,416.436 389.435,358.191L310.423,321.724Z"
android:fillColor="#33FF88"/>
<path
android:pathData="M212.667,79.232H296.257L426.001,442.31H327.539L303.949,369.489H207.026L184.462,442.31H86L139.851,287.951H227.539H281.898L256.77,181.797H254.205L238.559,244.082L176.78,182.098L212.667,79.232Z"
android:fillColor="#00C853"
android:fillType="evenOdd"/>
<path
android:pathData="M389.433,358.196L310.421,321.729C301.312,349.89 287.34,358.841 255.487,369.493L321.862,424.793C348.112,412.353 372.62,390.966 389.433,358.196Z"
android:fillColor="#33FF88"/>
</group>
</vector>
<group
android:scaleX="0.12007617"
android:scaleY="0.12007617"
android:translateX="23.2605"
android:translateY="23.2605">
<path
android:fillColor="#33FF88"
android:pathData="M310.423,321.724C290.671,371.359 209.633,359.204 209.633,316.152V192.57C209.633,147.999 289.658,137.869 313.969,182.44L387.916,152.557C359.046,88.74 305.865,70 261.801,70C189.88,70 118.972,111.532 118.972,192.57V316.152C118.972,397.696 189.88,438.722 260.281,438.722C305.359,438.722 359.553,416.436 389.435,358.191L310.423,321.724Z" />
<path
android:fillColor="#00C853"
android:fillType="evenOdd"
android:pathData="M212.667,79.232H296.257L426.001,442.31H327.539L303.949,369.489H207.026L184.462,442.31H86L139.851,287.951H227.539H281.898L256.77,181.797H254.205L238.559,244.082L176.78,182.098L212.667,79.232Z" />
<path
android:fillColor="#33FF88"
android:pathData="M389.433,358.196L310.421,321.729C301.312,349.89 287.34,358.841 255.487,369.493L321.862,424.793C348.112,412.353 372.62,390.966 389.433,358.196Z" />
</group>
</vector>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:id="@+id/commit_sha"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true" />
<TextView
android:id="@+id/commit_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:textSize="16sp" />
</LinearLayout>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="ic_launcher_background">#FFFFFFFF</color>
</resources>

View file

@ -1,6 +1,6 @@
<resources>
<string name="app_name">Aliucord Manager</string>
<string name="clear_files_cache">Clear files cache (Aliucord.dex and patched manifest)</string>
<string name="clear_files_cache">Clear files cache (Injector.dex and patched manifest)</string>
<string name="commits">Commits</string>
<string name="dex_location">Dex location</string>
<string name="open_settings">Open settings</string>
@ -13,15 +13,17 @@
<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="plugin_title">%1$s v%2$s</string>
<string name="plugin_authors">By %1$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_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>
<string name="home">Home</string>
<string name="install">Install</string>
<string name="reinstall">Reinstall</string>
<string name="update">Update</string>
@ -30,5 +32,13 @@
<string name="about">About</string>
<string name="team">Team</string>
<string name="contributors">Contributors</string>
<string name="loading">Loading...</string>
</resources>
<string name="loading">Loading…</string>
<string name="search_plugins">Search Plugins</string>
<string name="source_code">Source Code</string>
<string name="installer">Installer</string>
<string name="use_black">Use black</string>
<string name="use_black_description">Use black colors for dark mode</string>
<string name="theme_setting_description">Manager appearance</string>
<string name="no_plugins_installed">No plugins installed!</string>
<string name="store">Store</string>
</resources>

View file

@ -2,6 +2,5 @@
<resources>
<style name="Theme.AliucordManager" parent="Theme.MaterialComponents.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="android:listDivider">@drawable/divider</item>
</style>
</resources>

View file

@ -10,14 +10,14 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.0-alpha03")
@Suppress("GradleDependency") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
classpath("com.android.tools.build:gradle:7.1.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
}
}
tasks.register("clean", Delete::class) {
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
}

View file

@ -1,6 +1,6 @@
#Mon Jul 12 01:52:20 CEST 2021
#Tue Jan 25 18:24:43 EST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

0
gradlew vendored Normal file → Executable file
View file

View file

@ -10,5 +10,6 @@ dependencyResolutionManagement {
mavenCentral()
}
}
rootProject.name = "Aliucord Manager"
include(":app")
include(":app")