Overhauled UI and refactoring
This commit is contained in:
parent
b05ae80894
commit
f0cd29c500
55 changed files with 1642 additions and 1065 deletions
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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>
|
||||
20
app/src/main/java/com/aliucord/manager/data/DataStore.kt
Normal file
20
app/src/main/java/com/aliucord/manager/data/DataStore.kt
Normal 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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
110
app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt
Normal file
110
app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt
Normal 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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -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") }
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
)
|
||||
|
|
@ -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),
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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}") },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}"
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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" />
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -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
0
gradlew
vendored
Normal file → Executable file
|
|
@ -10,5 +10,6 @@ dependencyResolutionManagement {
|
|||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Aliucord Manager"
|
||||
include(":app")
|
||||
include(":app")
|
||||
Loading…
Add table
Reference in a new issue