diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 730dcf6..0e5dfcd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1bb1282..e4d7799 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+ android:exported="true">
@@ -39,4 +39,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/data/DataStore.kt b/app/src/main/java/com/aliucord/manager/data/DataStore.kt
new file mode 100644
index 0000000..cac5725
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/data/DataStore.kt
@@ -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 by preferencesDataStore("settings")
+ }
+
+ init {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/models/Commit.kt b/app/src/main/java/com/aliucord/manager/models/Commit.kt
index 9e07eed..fef3c64 100644
--- a/app/src/main/java/com/aliucord/manager/models/Commit.kt
+++ b/app/src/main/java/com/aliucord/manager/models/Commit.kt
@@ -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,
diff --git a/app/src/main/java/com/aliucord/manager/models/GithubUser.kt b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt
index ce925da..1eb55ac 100644
--- a/app/src/main/java/com/aliucord/manager/models/GithubUser.kt
+++ b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt
@@ -1,3 +1,8 @@
package com.aliucord.manager.models
-data class GithubUser(val login: String, val contributions: Int)
\ No newline at end of file
+import com.google.gson.annotations.SerializedName
+
+data class GithubUser(
+ @SerializedName("login") val name: String,
+ val contributions: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt b/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt
index e02b538..2fd9832 100644
--- a/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt
+++ b/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt
@@ -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(
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
-)
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt b/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt
index f0e78a3..5c64556 100644
--- a/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt
+++ b/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt
@@ -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")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt b/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt
index a79e48e..99a8cfd 100644
--- a/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt
@@ -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
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/Screen.kt b/app/src/main/java/com/aliucord/manager/ui/Screen.kt
index 5f0f6b7..c1b55c9 100644
--- a/app/src/main/java/com/aliucord/manager/ui/Screen.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/Screen.kt
@@ -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)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt b/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt
new file mode 100644
index 0000000..d8824a1
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt
@@ -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)) }
+ )
+ }
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt b/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt
deleted file mode 100644
index 8bd569a..0000000
--- a/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt
+++ /dev/null
@@ -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, modifier: Modifier = Modifier) {
- val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
- var isLoading by remember { mutableStateOf(true) }
- val commits = remember { mutableStateListOf() }
-
- 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, private val dark: Boolean) : RecyclerView.Adapter() {
- 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
-}
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt b/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt
deleted file mode 100644
index 71065b5..0000000
--- a/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt
+++ /dev/null
@@ -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,
- 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)
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt b/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt
new file mode 100644
index 0000000..e7d1d0a
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt
@@ -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))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt b/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt
new file mode 100644
index 0000000..e313295
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt
@@ -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))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt b/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt
new file mode 100644
index 0000000..e93fd2d
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt
@@ -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
+ )
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt
new file mode 100644
index 0000000..a031790
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt
@@ -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") }
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt
new file mode 100644
index 0000000..44b4ec7
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt
@@ -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?>(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)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt
new file mode 100644
index 0000000..0c1189b
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt
@@ -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")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt
new file mode 100644
index 0000000..38ea1ec
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt
@@ -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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt b/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt
new file mode 100644
index 0000000..e529925
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt
@@ -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,
+ 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 }
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt b/app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt
similarity index 64%
rename from app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt
rename to app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt
index def3578..b6349a8 100644
--- a/app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt
@@ -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) {
- val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
+fun Changelog(plugin: Plugin, onDismiss: () -> Unit) {
val imageBitmap = remember {
mutableStateOf(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) {
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) {
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) {
}
}
}
- 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) {
@Composable
private fun LinkText(annotatedString: AnnotatedString, modifier: Modifier = Modifier) {
val urlHandler = LocalUriHandler.current
+
ClickableText(
text = annotatedString,
style = TextStyle(color = LocalContentColor.current),
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt
new file mode 100644
index 0000000..0203414
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt
@@ -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
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt b/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt
new file mode 100644
index 0000000..16d9d91
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt
@@ -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
+) = Switch(
+ modifier = modifier,
+ checked = preference.get(),
+ onCheckedChange = preference::set
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt b/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt
new file mode 100644
index 0000000..a23d67d
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt
@@ -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) {
+ 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)
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt
index 72662b6..6074550 100644
--- a/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt
@@ -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?>(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") }
- )
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt
new file mode 100644
index 0000000..bfe6e7e
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt
@@ -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() }
+
+ 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}") },
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt
index d3d5cc9..8c2c170 100644
--- a/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt
@@ -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(null) }
val versions = remember { mutableStateOf(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()
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt
new file mode 100644
index 0000000..2f884e3
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt
@@ -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
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt
deleted file mode 100644
index 7e5b2d0..0000000
--- a/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt
+++ /dev/null
@@ -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
- )
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt
index 7dcdff5..31206e2 100644
--- a/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt
@@ -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(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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt
new file mode 100644
index 0000000..101cadc
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt
@@ -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 {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt
index 9cbe805..331bc6b 100644
--- a/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt
@@ -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
\ No newline at end of file
+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)
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt
deleted file mode 100644
index de1a2bb..0000000
--- a/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt
+++ /dev/null
@@ -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)
-)
diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt
index 58d1867..7941fb3 100644
--- a/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt
@@ -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]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt
index ee4418d..65b0c63 100644
--- a/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt
+++ b/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt
@@ -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()
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/utils/Constants.kt b/app/src/main/java/com/aliucord/manager/utils/Constants.kt
index b0c0d31..c1ffe16 100644
--- a/app/src/main/java/com/aliucord/manager/utils/Constants.kt
+++ b/app/src/main/java/com/aliucord/manager/utils/Constants.kt
@@ -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()
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt b/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt
new file mode 100644
index 0000000..dfdd773
--- /dev/null
+++ b/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt
@@ -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()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/utils/Downloader.kt b/app/src/main/java/com/aliucord/manager/utils/Downloader.kt
deleted file mode 100644
index 06ef186..0000000
--- a/app/src/main/java/com/aliucord/manager/utils/Downloader.kt
+++ /dev/null
@@ -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)
- }
- }
-}
diff --git a/app/src/main/java/com/aliucord/manager/utils/Github.kt b/app/src/main/java/com/aliucord/manager/utils/Github.kt
index ca6b681..6500877 100644
--- a/app/src/main/java/com/aliucord/manager/utils/Github.kt
+++ b/app/src/main/java/com/aliucord/manager/utils/Github.kt
@@ -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>()
private val commitsType = object : TypeToken>() {}.type
private val contributorsType = object : TypeToken>() {}.type
- private val gson = Gson()
fun getCommits(params: Map?): Array {
// 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>(
- it,
- commitsType
- )
+ gson.fromJson>(it, commitsType)
}
commitsCache[query] = res
return res
}
- val contributors: Array by lazy {
+ val contributors: Array by lazy() {
InputStreamReader(URL(contributorsUrl).openStream()).use { stream ->
- gson.fromJson>(
- stream,
- contributorsType
- ).apply {
+ gson.fromJson>(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}"
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aliucord/manager/utils/Plugin.kt b/app/src/main/java/com/aliucord/manager/utils/Plugin.kt
index 128f80d..cedba47 100644
--- a/app/src/main/java/com/aliucord/manager/utils/Plugin.kt
+++ b/app/src/main/java/com/aliucord/manager/utils/Plugin.kt
@@ -9,9 +9,10 @@ import java.util.zip.ZipFile
data class Manifest(
val name: String,
- val authors: Array,
+ val authors: ArrayList,
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(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 }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml
deleted file mode 100644
index c646b35..0000000
--- a/app/src/main/res/drawable/divider.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_account_github_white_24dp.xml b/app/src/main/res/drawable/ic_account_github_white_24dp.xml
new file mode 100644
index 0000000..e4b5e91
--- /dev/null
+++ b/app/src/main/res/drawable/ic_account_github_white_24dp.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_discord.xml b/app/src/main/res/drawable/ic_discord.xml
index 7529205..0badb62 100644
--- a/app/src/main/res/drawable/ic_discord.xml
+++ b/app/src/main/res/drawable/ic_discord.xml
@@ -3,11 +3,10 @@
android:height="19dp"
android:viewportWidth="71"
android:viewportHeight="55">
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_download_24dp.xml b/app/src/main/res/drawable/ic_download_24dp.xml
index f80cf84..1acdd9b 100644
--- a/app/src/main/res/drawable/ic_download_24dp.xml
+++ b/app/src/main/res/drawable/ic_download_24dp.xml
@@ -1,9 +1,9 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_history_white_24dp.xml b/app/src/main/res/drawable/ic_history_white_24dp.xml
index 4bc8bde..b4a1e1d 100644
--- a/app/src/main/res/drawable/ic_history_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_history_white_24dp.xml
@@ -1,9 +1,9 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index f0a6991..2f0c97e 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -3,19 +3,20 @@
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/commit.xml b/app/src/main/res/layout/commit.xml
deleted file mode 100644
index 3ea4837..0000000
--- a/app/src/main/res/layout/commit.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/layout/recycler.xml b/app/src/main/res/layout/recycler.xml
deleted file mode 100644
index 634b8cd..0000000
--- a/app/src/main/res/layout/recycler.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
index c5d5899..a5b623a 100644
--- a/app/src/main/res/values/ic_launcher_background.xml
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -1,4 +1,4 @@
- #FFFFFF
+ #FFFFFFFF
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7d248f6..1c30b45 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
Aliucord Manager
- Clear files cache (Aliucord.dex and patched manifest)
+ Clear files cache (Injector.dex and patched manifest)
Commits
Dex location
Open settings
@@ -13,15 +13,17 @@
Use Aliucord.dex from storage
Plugins
Launch Plugins Page
- %1$s v%2$s by %3$s
+ %1$s v%2$s
+ By %1$s
Delete %1$s
Search
View changelog for %1$s
- Are you sure you want to delete this plugin? This action cannot be undone.
+ Are you sure you want to uninstall this plugin? This action cannot be undone.
Delete
Close
Support Server
Back
+ Home
Install
Reinstall
Update
@@ -30,5 +32,13 @@
About
Team
Contributors
- Loading...
-
+ Loading…
+ Search Plugins
+ Source Code
+ Installer
+ Use black
+ Use black colors for dark mode
+ Manager appearance
+ No plugins installed!
+ Store
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 8a3fc43..89d42b9 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -2,6 +2,5 @@
diff --git a/build.gradle.kts b/build.gradle.kts
index 564619f..2107f69 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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("clean") {
delete(rootProject.buildDir)
-}
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c0b2dd..5500c88 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/settings.gradle.kts b/settings.gradle.kts
index defd771..07c94aa 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -10,5 +10,6 @@ dependencyResolutionManagement {
mavenCentral()
}
}
+
rootProject.name = "Aliucord Manager"
-include(":app")
+include(":app")
\ No newline at end of file